// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Microsoft.CodeAnalysis
{
///
/// The collection of extension methods for the type
///
internal static class ImmutableArrayExtensions
{
///
/// Converts a sequence to an immutable array.
///
/// Elemental type of the sequence.
/// The sequence to convert.
/// An immutable copy of the contents of the sequence.
/// If items is null (default)
/// If the sequence is null, this will throw
public static ImmutableArray AsImmutable(this IEnumerable items)
{
return ImmutableArray.CreateRange(items);
}
///
/// Converts a sequence to an immutable array.
///
/// Elemental type of the sequence.
/// The sequence to convert.
/// An immutable copy of the contents of the sequence.
/// If the sequence is null, this will return an empty array.
public static ImmutableArray AsImmutableOrEmpty(this IEnumerable items)
{
if (items == null)
{
return ImmutableArray.Empty;
}
return ImmutableArray.CreateRange(items);
}
///
/// Converts a sequence to an immutable array.
///
/// Elemental type of the sequence.
/// The sequence to convert.
/// An immutable copy of the contents of the sequence.
/// If the sequence is null, this will return the default (null) array.
public static ImmutableArray AsImmutableOrNull(this IEnumerable items)
{
if (items == null)
{
return default(ImmutableArray);
}
return ImmutableArray.CreateRange(items);
}
///
/// Converts an array to an immutable array. The array must not be null.
///
///
/// The sequence to convert
///
public static ImmutableArray AsImmutable(this T[] items)
{
Debug.Assert(items != null);
return ImmutableArray.Create(items);
}
///
/// Converts a array to an immutable array.
///
///
/// The sequence to convert
///
/// If the sequence is null, this will return the default (null) array.
public static ImmutableArray AsImmutableOrNull(this T[] items)
{
if (items == null)
{
return default(ImmutableArray);
}
return ImmutableArray.Create(items);
}
///
/// Converts an array to an immutable array.
///
///
/// The sequence to convert
/// If the array is null, this will return an empty immutable array.
public static ImmutableArray AsImmutableOrEmpty(this T[] items)
{
if (items == null)
{
return ImmutableArray.Empty;
}
return ImmutableArray.Create(items);
}
///
/// Reads bytes from specified .
///
/// The stream.
/// Read-only content of the stream.
public static ImmutableArray ToImmutable(this MemoryStream stream)
{
return ImmutableArray.Create(stream.ToArray());
}
///
/// Maps an immutable array to another immutable array.
///
///
///
/// The array to map
/// The mapping delegate
/// If the items's length is 0, this will return an empty immutable array
public static ImmutableArray SelectAsArray(this ImmutableArray items, Func map)
{
return ImmutableArray.CreateRange(items, map);
}
///
/// Maps an immutable array to another immutable array.
///
///
///
///
/// The sequence to map
/// The mapping delegate
/// The extra input used by mapping delegate
/// If the items's length is 0, this will return an empty immutable array.
public static ImmutableArray SelectAsArray(this ImmutableArray items, Func map, TArg arg)
{
return ImmutableArray.CreateRange(items, map, arg);
}
///
/// Maps an immutable array to another immutable array.
///
///
///
///
/// The sequence to map
/// The mapping delegate
/// The extra input used by mapping delegate
/// If the items's length is 0, this will return an empty immutable array.
public static ImmutableArray SelectAsArray(this ImmutableArray items, Func map, TArg arg)
{
switch (items.Length)
{
case 0:
return ImmutableArray.Empty;
case 1:
return ImmutableArray.Create(map(items[0], 0, arg));
case 2:
return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg));
case 3:
return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg), map(items[2], 2, arg));
case 4:
return ImmutableArray.Create(map(items[0], 0, arg), map(items[1], 1, arg), map(items[2], 2, arg), map(items[3], 3, arg));
default:
var builder = ArrayBuilder.GetInstance(items.Length);
for (int i = 0; i < items.Length; i++)
{
builder.Add(map(items[i], i, arg));
}
return builder.ToImmutableAndFree();
}
}
///
/// Creates a new immutable array based on filtered elements by the predicate. The array must not be null.
///
///
/// The array to process
/// The delegate that defines the conditions of the element to search for.
///
public static ImmutableArray WhereAsArray(this ImmutableArray array, Func predicate)
{
Debug.Assert(!array.IsDefault);
ArrayBuilder builder = null;
bool none = true;
bool all = true;
int n = array.Length;
for (int i = 0; i < n; i++)
{
var a = array[i];
if (predicate(a))
{
none = false;
if (all)
{
continue;
}
Debug.Assert(i > 0);
if (builder == null)
{
builder = ArrayBuilder.GetInstance();
}
builder.Add(a);
}
else
{
if (none)
{
all = false;
continue;
}
Debug.Assert(i > 0);
if (all)
{
Debug.Assert(builder == null);
all = false;
builder = ArrayBuilder.GetInstance();
for (int j = 0; j < i; j++)
{
builder.Add(array[j]);
}
}
}
}
if (builder != null)
{
Debug.Assert(!all);
Debug.Assert(!none);
return builder.ToImmutableAndFree();
}
else if (all)
{
return array;
}
else
{
Debug.Assert(none);
return ImmutableArray.Empty;
}
}
///
/// Casts the immutable array of a Type to an immutable array of its base type.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ImmutableArray Cast(this ImmutableArray items)
where TDerived : class, TBase
{
return ImmutableArray.CastUp(items);
}
///
/// Determines whether this instance and another immutable array are equal.
///
///
///
///
/// The comparer to determine if the two arrays are equal.
/// True if the two arrays are equal
public static bool SetEquals(this ImmutableArray array1, ImmutableArray array2, IEqualityComparer comparer)
{
if (array1.IsDefault)
{
return array2.IsDefault;
}
else if (array2.IsDefault)
{
return false;
}
var count1 = array1.Length;
var count2 = array2.Length;
// avoid constructing HashSets in these common cases
if (count1 == 0)
{
return count2 == 0;
}
else if (count2 == 0)
{
return false;
}
else if (count1 == 1 && count2 == 1)
{
var item1 = array1[0];
var item2 = array2[0];
return comparer.Equals(item1, item2);
}
var set1 = new HashSet(array1, comparer);
var set2 = new HashSet(array2, comparer);
// internally recognizes that set2 is a HashSet with the same comparer (http://msdn.microsoft.com/en-us/library/bb346516.aspx)
return set1.SetEquals(set2);
}
///
/// Returns an empty array if the input array is null (default)
///
public static ImmutableArray NullToEmpty(this ImmutableArray array)
{
return array.IsDefault ? ImmutableArray.Empty : array;
}
///
/// Returns an array of distinct elements, preserving the order in the original array.
/// If the array has no duplicates, the original array is returned. The original array must not be null.
///
public static ImmutableArray Distinct(this ImmutableArray array, IEqualityComparer comparer = null)
{
Debug.Assert(!array.IsDefault);
if (array.Length < 2)
{
return array;
}
var set = new HashSet(comparer);
var builder = ArrayBuilder.GetInstance();
foreach (var a in array)
{
if (set.Add(a))
{
builder.Add(a);
}
}
var result = (builder.Count == array.Length) ? array : builder.ToImmutable();
builder.Free();
return result;
}
internal static bool HasAnyErrors(this ImmutableArray diagnostics) where T : Diagnostic
{
foreach (var diagnostic in diagnostics)
{
if (diagnostic.Severity == DiagnosticSeverity.Error)
{
return true;
}
}
return false;
}
// Swap the first and last elements of a read-only array, yielding a new read only array.
// Used in DEBUG to make sure that read-only array is not sorted.
internal static ImmutableArray DeOrder(this ImmutableArray array)
{
if (!array.IsDefault && array.Length >= 2)
{
T[] copy = array.ToArray();
int last = copy.Length - 1;
var temp = copy[0];
copy[0] = copy[last];
copy[last] = temp;
return copy.AsImmutable();
}
else
{
return array;
}
}
internal static ImmutableArray Flatten(
this Dictionary> dictionary,
IComparer comparer = null)
{
if (dictionary.Count == 0)
{
return ImmutableArray.Empty;
}
var builder = ArrayBuilder.GetInstance();
foreach (var kvp in dictionary)
{
builder.AddRange(kvp.Value);
}
if (comparer != null && builder.Count > 1)
{
// PERF: Beware ImmutableArray.Builder.Sort allocates a Comparer wrapper object
builder.Sort(comparer);
}
return builder.ToImmutableAndFree();
}
internal static ImmutableArray Concat(this ImmutableArray first, ImmutableArray second)
{
return first.AddRange(second);
}
internal static bool HasDuplicates(this ImmutableArray array, IEqualityComparer comparer)
{
switch (array.Length)
{
case 0:
case 1:
return false;
case 2:
return comparer.Equals(array[0], array[1]);
default:
var set = new HashSet(comparer);
foreach (var i in array)
{
if (!set.Add(i))
{
return true;
}
}
return false;
}
}
public static int Count(this ImmutableArray items, Func predicate)
{
if (items.IsEmpty)
{
return 0;
}
int count = 0;
for (int i = 0; i < items.Length; ++i)
{
if (predicate(items[i]))
{
++count;
}
}
return count;
}
internal static Dictionary> ToDictionary(this ImmutableArray items, Func keySelector, IEqualityComparer comparer = null)
{
if (items.Length == 1)
{
var dictionary = new Dictionary>(1, comparer);
T value = items[0];
dictionary.Add(keySelector(value), ImmutableArray.Create(value));
return dictionary;
}
else
{
// bucketize
// prevent reallocation. it may not have 'count' entries, but it won't have more.
var accumulator = new Dictionary>(items.Length, comparer);
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var key = keySelector(item);
ArrayBuilder bucket;
if (!accumulator.TryGetValue(key, out bucket))
{
bucket = ArrayBuilder.GetInstance();
accumulator.Add(key, bucket);
}
bucket.Add(item);
}
var dictionary = new Dictionary>(accumulator.Count, comparer);
// freeze
foreach (var pair in accumulator)
{
dictionary.Add(pair.Key, pair.Value.ToImmutableAndFree());
}
return dictionary;
}
}
}
}