|
| 1 | +// Copyright (c) Microsoft. All rights reserved. |
| 2 | +// Licensed under the MIT license. See LICENSE file in the project root for full license information. |
| 3 | + |
| 4 | +namespace System.Collections.Generic |
| 5 | +{ |
| 6 | + /// <summary>Internal helper functions for working with enumerables.</summary> |
| 7 | + internal static class EnumerableHelpers |
| 8 | + { |
| 9 | + /// <summary>Converts an enumerable to an array using the same logic as does List{T}.</summary> |
| 10 | + /// <param name="length">The number of items stored in the resulting array, 0-indexed.</param> |
| 11 | + /// <returns> |
| 12 | + /// The resulting array. The length of the array may be greater than <paramref name="length"/>, |
| 13 | + /// which is the actual number of elements in the array. |
| 14 | + /// </returns> |
| 15 | + internal static T[] ToArray<T>(IEnumerable<T> source, out int length) |
| 16 | + { |
| 17 | + T[] arr; |
| 18 | + int count = 0; |
| 19 | + |
| 20 | + ICollection<T> ic = source as ICollection<T>; |
| 21 | + if (ic != null) |
| 22 | + { |
| 23 | + count = ic.Count; |
| 24 | + if (count == 0) |
| 25 | + { |
| 26 | + arr = Array.Empty<T>(); |
| 27 | + } |
| 28 | + else |
| 29 | + { |
| 30 | + // Allocate an array of the desired size, then copy the elements into it. Note that this has the same |
| 31 | + // issue regarding concurrency as other existing collections like List<T>. If the collection size |
| 32 | + // concurrently changes between the array allocation and the CopyTo, we could end up either getting an |
| 33 | + // exception from overrunning the array (if the size went up) or we could end up not filling as many |
| 34 | + // items as 'count' suggests (if the size went down). This is only an issue for concurrent collections |
| 35 | + // that implement ICollection<T>, which as of .NET 4.6 is just ConcurrentDictionary<TKey, TValue>. |
| 36 | + arr = new T[count]; |
| 37 | + ic.CopyTo(arr, 0); |
| 38 | + } |
| 39 | + } |
| 40 | + else |
| 41 | + { |
| 42 | + arr = Array.Empty<T>(); |
| 43 | + foreach (var item in source) |
| 44 | + { |
| 45 | + if (count == arr.Length) |
| 46 | + { |
| 47 | + // MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR. |
| 48 | + // It represents the maximum number of elements that can be in an array where |
| 49 | + // the size of the element is greater than one byte; a separate, slightly larger constant, |
| 50 | + // is used when the size of the element is one. |
| 51 | + const int MaxArrayLength = 0x7FEFFFFF; |
| 52 | + |
| 53 | + // This is the same growth logic as in List<T>: |
| 54 | + // If the array is currently empty, we make it a default size. Otherwise, we attempt to |
| 55 | + // double the size of the array. Doubling will overflow once the size of the array reaches |
| 56 | + // 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead |
| 57 | + // constrain the length to be MaxArrayLength (this overflow check works because of of the |
| 58 | + // cast to uint). Because a slightly larger constant is used when T is one byte in size, we |
| 59 | + // could then end up in a situation where arr.Length is MaxArrayLength or slightly larger, such |
| 60 | + // that we constrain newLength to be MaxArrayLength but the needed number of elements is actually |
| 61 | + // larger than that. For that case, we then ensure that the newLength is large enough to hold |
| 62 | + // the desired capacity. This does mean that in the very rare case where we've grown to such a |
| 63 | + // large size, each new element added after MaxArrayLength will end up doing a resize. |
| 64 | + const int DefaultCapacity = 4; |
| 65 | + int newLength = count == 0 ? DefaultCapacity : count * 2; |
| 66 | + if ((uint)newLength > MaxArrayLength) |
| 67 | + { |
| 68 | + newLength = MaxArrayLength; |
| 69 | + } |
| 70 | + if (newLength < count + 1) |
| 71 | + { |
| 72 | + newLength = count + 1; |
| 73 | + } |
| 74 | + |
| 75 | + arr = ArrayT<T>.Resize(arr, newLength, count); |
| 76 | + } |
| 77 | + arr[count++] = item; |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + length = count; |
| 82 | + return arr; |
| 83 | + } |
| 84 | + } |
| 85 | +} |
0 commit comments