// 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; } } } }