diff --git a/src/Common/src/System/Collections/Generic/EnumerableHelpers.cs b/src/Common/src/System/Collections/Generic/EnumerableHelpers.cs new file mode 100644 index 000000000000..6cd46fd32efd --- /dev/null +++ b/src/Common/src/System/Collections/Generic/EnumerableHelpers.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Collections.Generic +{ + /// Internal helper functions for working with enumerables. + internal static class EnumerableHelpers + { + /// Converts an enumerable to an array using the same logic as does List{T}. + /// The number of items stored in the resulting array, 0-indexed. + /// + /// The resulting array. The length of the array may be greater than , + /// which is the actual number of elements in the array. + /// + internal static T[] ToArray(IEnumerable source, out int length) + { + T[] arr; + int count = 0; + + ICollection ic = source as ICollection; + if (ic != null) + { + count = ic.Count; + if (count == 0) + { + arr = Array.Empty(); + } + else + { + // Allocate an array of the desired size, then copy the elements into it. Note that this has the same + // issue regarding concurrency as other existing collections like List. If the collection size + // concurrently changes between the array allocation and the CopyTo, we could end up either getting an + // exception from overrunning the array (if the size went up) or we could end up not filling as many + // items as 'count' suggests (if the size went down). This is only an issue for concurrent collections + // that implement ICollection, which as of .NET 4.6 is just ConcurrentDictionary. + arr = new T[count]; + ic.CopyTo(arr, 0); + } + } + else + { + arr = Array.Empty(); + foreach (var item in source) + { + if (count == arr.Length) + { + // MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR. + // It represents the maximum number of elements that can be in an array where + // the size of the element is greater than one byte; a separate, slightly larger constant, + // is used when the size of the element is one. + const int MaxArrayLength = 0x7FEFFFFF; + + // This is the same growth logic as in List: + // If the array is currently empty, we make it a default size. Otherwise, we attempt to + // double the size of the array. Doubling will overflow once the size of the array reaches + // 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead + // constrain the length to be MaxArrayLength (this overflow check works because of of the + // cast to uint). Because a slightly larger constant is used when T is one byte in size, we + // could then end up in a situation where arr.Length is MaxArrayLength or slightly larger, such + // that we constrain newLength to be MaxArrayLength but the needed number of elements is actually + // larger than that. For that case, we then ensure that the newLength is large enough to hold + // the desired capacity. This does mean that in the very rare case where we've grown to such a + // large size, each new element added after MaxArrayLength will end up doing a resize. + const int DefaultCapacity = 4; + int newLength = count == 0 ? DefaultCapacity : count * 2; + if ((uint)newLength > MaxArrayLength) + { + newLength = MaxArrayLength; + } + if (newLength < count + 1) + { + newLength = count + 1; + } + + arr = ArrayT.Resize(arr, newLength, count); + } + arr[count++] = item; + } + } + + length = count; + return arr; + } + } +} diff --git a/src/System.Collections/src/System.Collections.csproj b/src/System.Collections/src/System.Collections.csproj index b3eff2b74d43..d0d7de672979 100644 --- a/src/System.Collections/src/System.Collections.csproj +++ b/src/System.Collections/src/System.Collections.csproj @@ -42,11 +42,14 @@ + + Common\System\ArrayT.cs + Common\System\Collections\HashHelpers.cs - - Common\System\ArrayT.cs + + Common\System\Collections\Generic\EnumerableHelpers.cs diff --git a/src/System.Collections/src/System/Collections/Generic/Queue.cs b/src/System.Collections/src/System/Collections/Generic/Queue.cs index 3677c0a94a4f..8b8fb66fe76b 100644 --- a/src/System.Collections/src/System/Collections/Generic/Queue.cs +++ b/src/System.Collections/src/System/Collections/Generic/Queue.cs @@ -32,14 +32,13 @@ public class Queue : IEnumerable, private const int MinimumGrow = 4; private const int GrowFactor = 200; // double each time private const int DefaultCapacity = 4; - private static T[] s_emptyArray = Array.Empty(); // Creates a queue with room for capacity objects. The default initial // capacity and grow factor are used. /// public Queue() { - _array = s_emptyArray; + _array = Array.Empty(); } // Creates a queue with room for capacity objects. The default grow factor @@ -50,11 +49,7 @@ public Queue(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity", SR.ArgumentOutOfRange_NeedNonNegNumRequired); - _array = new T[capacity]; - _head = 0; - _tail = 0; - _size = 0; } // Fills a Queue with the elements of an ICollection. Uses the enumerator @@ -67,8 +62,6 @@ public Queue(IEnumerable collection) throw new ArgumentNullException("collection"); _array = new T[DefaultCapacity]; - _size = 0; - _version = 0; using (IEnumerator en = collection.GetEnumerator()) { diff --git a/src/System.Collections/src/System/Collections/Generic/SortedSet.cs b/src/System.Collections/src/System/Collections/Generic/SortedSet.cs index 3e4c7485ab23..bca8f9c5f6c0 100644 --- a/src/System.Collections/src/System/Collections/Generic/SortedSet.cs +++ b/src/System.Collections/src/System/Collections/Generic/SortedSet.cs @@ -95,13 +95,9 @@ public SortedSet(IEnumerable collection, IComparer comparer) //breadth first traversal to recreate nodes if (baseSortedSet.Count == 0) { - _count = 0; - _version = 0; - _root = null; return; } - //pre order way to replicate nodes Stack theirStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); Stack myStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); @@ -138,23 +134,27 @@ public SortedSet(IEnumerable collection, IComparer comparer) } } _count = baseSortedSet._count; - _version = 0; } else - { //As it stands, you're doing an NlogN sort of the collection - List els = new List(collection); - els.Sort(_comparer); - for (int i = 1; i < els.Count; i++) + { + int count; + T[] els = EnumerableHelpers.ToArray(collection, out count); + if (count > 0) { - if (comparer.Compare(els[i], els[i - 1]) == 0) + Array.Sort(els, 0, count, _comparer); + int index = 1; + for (int i = 1; i < count; i++) { - els.RemoveAt(i); - i--; + if (comparer.Compare(els[i], els[i - 1]) != 0) + { + els[index++] = els[i]; + } } + count = index; + + _root = ConstructRootFromSortedArray(els, 0, count - 1, null); + _count = count; } - _root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); - _count = els.Count; - _version = 0; } } @@ -257,25 +257,24 @@ internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action) return true; } - List processQueue = new List(); - processQueue.Add(_root); + Queue processQueue = new Queue(); + processQueue.Enqueue(_root); Node current; while (processQueue.Count != 0) { - current = processQueue[0]; - processQueue.RemoveAt(0); + current = processQueue.Dequeue(); if (!action(current)) { return false; } if (current.Left != null) { - processQueue.Add(current.Left); + processQueue.Enqueue(current.Left); } if (current.Right != null) { - processQueue.Add(current.Right); + processQueue.Enqueue(current.Right); } } return true; @@ -2084,25 +2083,24 @@ internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action) return true; } - List processQueue = new List(); - processQueue.Add(_root); + Queue processQueue = new Queue(); + processQueue.Enqueue(_root); Node current; while (processQueue.Count != 0) { - current = processQueue[0]; - processQueue.RemoveAt(0); + current = processQueue.Dequeue(); if (IsWithinRange(current.Item) && !action(current)) { return false; } if (current.Left != null && (!_lBoundActive || Comparer.Compare(_min, current.Item) < 0)) { - processQueue.Add(current.Left); + processQueue.Enqueue(current.Left); } if (current.Right != null && (!_uBoundActive || Comparer.Compare(_max, current.Item) > 0)) { - processQueue.Add(current.Right); + processQueue.Enqueue(current.Right); } } return true; diff --git a/src/System.Collections/src/System/Collections/Generic/Stack.cs b/src/System.Collections/src/System/Collections/Generic/Stack.cs index 9e607a6fbb04..7d3731647704 100644 --- a/src/System.Collections/src/System/Collections/Generic/Stack.cs +++ b/src/System.Collections/src/System/Collections/Generic/Stack.cs @@ -29,14 +29,11 @@ public class Stack : IEnumerable, private Object _syncRoot; private const int DefaultCapacity = 4; - private static T[] s_emptyArray = Array.Empty(); /// public Stack() { - _array = s_emptyArray; - _size = 0; - _version = 0; + _array = Array.Empty(); } // Create a stack with a specific initial capacity. The initial capacity @@ -47,8 +44,6 @@ public Stack(int capacity) if (capacity < 0) throw new ArgumentOutOfRangeException("capacity", SR.ArgumentOutOfRange_NeedNonNegNumRequired); _array = new T[capacity]; - _size = 0; - _version = 0; } // Fills a Stack with the contents of a particular collection. The items are @@ -59,28 +54,7 @@ public Stack(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); - - ICollection c = collection as ICollection; - if (c != null) - { - int count = c.Count; - _array = new T[count]; - c.CopyTo(_array, 0); - _size = count; - } - else - { - _size = 0; - _array = new T[DefaultCapacity]; - - using (IEnumerator en = collection.GetEnumerator()) - { - while (en.MoveNext()) - { - Push(en.Current); - } - } - } + _array = EnumerableHelpers.ToArray(collection, out _size); } ///