Replace LINQ's custom Set with HashSet (#49591)
authorStephen Toub <stoub@microsoft.com>
Mon, 15 Mar 2021 13:02:57 +0000 (09:02 -0400)
committerGitHub <noreply@github.com>
Mon, 15 Mar 2021 13:02:57 +0000 (09:02 -0400)
16 files changed:
src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Helpers.cs [deleted file]
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/JaggedArray.cs [new file with mode: 0644]
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/ExceptQueryOperator.cs
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/IntersectQueryOperator.cs
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/UnionQueryOperator.cs
src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Unary/DistinctQueryOperator.cs
src/libraries/System.Linq/src/System.Linq.csproj
src/libraries/System.Linq/src/System/Linq/Distinct.SpeedOpt.cs
src/libraries/System.Linq/src/System/Linq/Distinct.cs
src/libraries/System.Linq/src/System/Linq/Except.cs
src/libraries/System.Linq/src/System/Linq/Intersect.cs
src/libraries/System.Linq/src/System/Linq/Set.cs [deleted file]
src/libraries/System.Linq/src/System/Linq/ToCollection.cs
src/libraries/System.Linq/src/System/Linq/Union.SpeedOpt.cs
src/libraries/System.Linq/src/System/Linq/Union.cs

index c04ba4b..b78dd81 100644 (file)
@@ -18,6 +18,7 @@
     <Compile Include="System\Linq\Parallel\Enumerables\QueryAggregationOptions.cs" />
     <Compile Include="System\Linq\Parallel\Enumerables\RangeEnumerable.cs" />
     <Compile Include="System\Linq\Parallel\Enumerables\RepeatEnumerable.cs" />
+    <Compile Include="System\Linq\Parallel\JaggedArray.cs" />
     <Compile Include="System\Linq\Parallel\Merging\ArrayMergeHelper.cs" />
     <Compile Include="System\Linq\Parallel\Merging\AsynchronousChannelMergeEnumerator.cs" />
     <Compile Include="System\Linq\Parallel\Merging\DefaultMergeHelper.cs" />
     <Compile Include="System\Linq\Parallel\Scheduling\Scheduling.cs" />
     <Compile Include="System\Linq\Parallel\Scheduling\SpoolingTask.cs" />
     <Compile Include="System\Linq\Parallel\Scheduling\SpoolingTaskBase.cs" />
-    <Compile Include="System\Linq\Parallel\Helpers.cs" />
     <Compile Include="System\Linq\Parallel\Utils\CancellableEnumerable.cs" />
     <Compile Include="System\Linq\Parallel\Utils\ExceptionAggregator.cs" />
     <Compile Include="System\Linq\Parallel\Utils\ExchangeUtilities.cs" />
diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Helpers.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Helpers.cs
deleted file mode 100644 (file)
index 5449425..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-
-namespace System.Linq.Parallel
-{
-    internal class JaggedArray<TElement>
-    {
-        public static TElement[][] Allocate(int size1, int size2)
-        {
-            TElement[][] ret = new TElement[size1][];
-            for (int i = 0; i < size1; i++)
-                ret[i] = new TElement[size2];
-
-            return ret;
-        }
-    }
-
-    // Copied from Linq.
-    internal class Set<TElement>
-    {
-        private int[] _buckets;
-        private Slot[] _slots;
-        private int _count;
-        private readonly IEqualityComparer<TElement> _comparer;
-#if DEBUG
-        private bool _haveRemoved;
-#endif
-
-        private const int InitialSize = 7;
-        private const int HashCodeMask = 0x7FFFFFFF;
-
-        public Set(IEqualityComparer<TElement>? comparer)
-        {
-            if (comparer == null) comparer = EqualityComparer<TElement>.Default;
-            _comparer = comparer;
-            _buckets = new int[InitialSize];
-            _slots = new Slot[InitialSize];
-        }
-
-        // If value is not in set, add it and return true; otherwise return false
-        public bool Add(TElement value)
-        {
-#if DEBUG
-            Debug.Assert(!_haveRemoved, "This class is optimised for never calling Add after Remove. If your changes need to do so, undo that optimization.");
-#endif
-            return !Find(value, true);
-        }
-
-        // Check whether value is in set
-        public bool Contains(TElement value)
-        {
-            return Find(value, false);
-        }
-
-        // If value is in set, remove it and return true; otherwise return false
-        public bool Remove(TElement value)
-        {
-#if DEBUG
-            _haveRemoved = true;
-#endif
-            int hashCode = InternalGetHashCode(value);
-            int bucket = hashCode % _buckets.Length;
-            int last = -1;
-            for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].next)
-            {
-                if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, value))
-                {
-                    if (last < 0)
-                    {
-                        _buckets[bucket] = _slots[i].next + 1;
-                    }
-                    else
-                    {
-                        _slots[last].next = _slots[i].next;
-                    }
-                    _slots[i].hashCode = -1;
-                    _slots[i].value = default;
-                    _slots[i].next = -1;
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private bool Find(TElement value, bool add)
-        {
-            int hashCode = InternalGetHashCode(value);
-            for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].next)
-            {
-                if (_slots[i].hashCode == hashCode && _comparer.Equals(_slots[i].value, value)) return true;
-            }
-            if (add)
-            {
-                if (_count == _slots.Length) Resize();
-                int index = _count;
-                _count++;
-                int bucket = hashCode % _buckets.Length;
-                _slots[index].hashCode = hashCode;
-                _slots[index].value = value;
-                _slots[index].next = _buckets[bucket] - 1;
-                _buckets[bucket] = index + 1;
-            }
-            return false;
-        }
-
-        private void Resize()
-        {
-            int newSize = checked(_count * 2 + 1);
-            int[] newBuckets = new int[newSize];
-            Slot[] newSlots = new Slot[newSize];
-            Array.Copy(_slots, newSlots, _count);
-            for (int i = 0; i < _count; i++)
-            {
-                int bucket = newSlots[i].hashCode % newSize;
-                newSlots[i].next = newBuckets[bucket] - 1;
-                newBuckets[bucket] = i + 1;
-            }
-            _buckets = newBuckets;
-            _slots = newSlots;
-        }
-
-        internal int InternalGetHashCode(TElement value)
-        {
-            // Work around comparer implementations that throw when passed null
-            return (value == null) ? 0 : _comparer.GetHashCode(value) & HashCodeMask;
-        }
-
-        internal struct Slot
-        {
-            internal int hashCode;
-            internal int next;
-            internal TElement? value;
-        }
-    }
-}
diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/JaggedArray.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/JaggedArray.cs
new file mode 100644 (file)
index 0000000..54c74e8
--- /dev/null
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Linq.Parallel
+{
+    internal class JaggedArray<TElement>
+    {
+        public static TElement[][] Allocate(int size1, int size2)
+        {
+            TElement[][] ret = new TElement[size1][];
+            for (int i = 0; i < size1; i++)
+                ret[i] = new TElement[size2];
+
+            return ret;
+        }
+    }
+}
index 3df3f4e..5a0cd8a 100644 (file)
@@ -139,7 +139,7 @@ namespace System.Linq.Parallel
             private readonly QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, TLeftKey> _leftSource; // Left data source.
             private readonly QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, int> _rightSource; // Right data source.
             private readonly IEqualityComparer<TInputOutput>? _comparer; // A comparer used for equality checks/hash-coding.
-            private Set<TInputOutput>? _hashLookup; // The hash lookup, used to produce the distinct set.
+            private HashSet<TInputOutput>? _hashLookup; // The hash lookup, used to produce the distinct set.
             private readonly CancellationToken _cancellationToken;
             private Shared<int>? _outputLoopCount;
 
@@ -177,7 +177,7 @@ namespace System.Linq.Parallel
                 {
                     _outputLoopCount = new Shared<int>(0);
 
-                    _hashLookup = new Set<TInputOutput>(_comparer);
+                    _hashLookup = new HashSet<TInputOutput>(_comparer);
 
                     Pair<TInputOutput, NoKeyMemoizationRequired> rightElement = default(Pair<TInputOutput, NoKeyMemoizationRequired>);
                     int rightKeyUnused = default(int);
@@ -265,7 +265,7 @@ namespace System.Linq.Parallel
                 // Build the set out of the left data source, if we haven't already.
                 if (_outputEnumerator == null)
                 {
-                    Set<TInputOutput> rightLookup = new Set<TInputOutput>(_comparer);
+                    HashSet<TInputOutput> rightLookup = new HashSet<TInputOutput>(_comparer);
 
                     Pair<TInputOutput, NoKeyMemoizationRequired> rightElement = default(Pair<TInputOutput, NoKeyMemoizationRequired>);
                     int rightKeyUnused = default(int);
index 3b6230f..9954272 100644 (file)
@@ -128,7 +128,7 @@ namespace System.Linq.Parallel
             private readonly QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, TLeftKey> _leftSource; // Left data source.
             private readonly QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, int> _rightSource; // Right data source.
             private readonly IEqualityComparer<TInputOutput>? _comparer; // Comparer to use for equality/hash-coding.
-            private Set<TInputOutput>? _hashLookup; // The hash lookup, used to produce the intersection.
+            private HashSet<TInputOutput>? _hashLookup; // The hash lookup, used to produce the intersection.
             private readonly CancellationToken _cancellationToken;
             private Shared<int>? _outputLoopCount;
 
@@ -164,7 +164,7 @@ namespace System.Linq.Parallel
                 if (_hashLookup == null)
                 {
                     _outputLoopCount = new Shared<int>(0);
-                    _hashLookup = new Set<TInputOutput>(_comparer);
+                    _hashLookup = new HashSet<TInputOutput>(_comparer);
 
                     Pair<TInputOutput, NoKeyMemoizationRequired> rightElement = default(Pair<TInputOutput, NoKeyMemoizationRequired>);
                     int rightKeyUnused = default(int);
index 1d6cccf..06aabb4 100644 (file)
@@ -184,7 +184,7 @@ namespace System.Linq.Parallel
         {
             private QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, TLeftKey>? _leftSource; // Left data source.
             private QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, TRightKey>? _rightSource; // Right data source.
-            private Set<TInputOutput>? _hashLookup; // The hash lookup, used to produce the union.
+            private HashSet<TInputOutput>? _hashLookup; // The hash lookup, used to produce the union.
             private readonly CancellationToken _cancellationToken;
             private Shared<int>? _outputLoopCount;
             private readonly IEqualityComparer<TInputOutput>? _comparer;
@@ -216,7 +216,7 @@ namespace System.Linq.Parallel
             {
                 if (_hashLookup == null)
                 {
-                    _hashLookup = new Set<TInputOutput>(_comparer);
+                    _hashLookup = new HashSet<TInputOutput>(_comparer);
                     _outputLoopCount = new Shared<int>(0);
                 }
 
index 4608cec..2621cdb 100644 (file)
@@ -119,7 +119,7 @@ namespace System.Linq.Parallel
         private class DistinctQueryOperatorEnumerator<TKey> : QueryOperatorEnumerator<TInputOutput, int>
         {
             private readonly QueryOperatorEnumerator<Pair<TInputOutput, NoKeyMemoizationRequired>, TKey> _source; // The data source.
-            private readonly Set<TInputOutput> _hashLookup; // The hash lookup, used to produce the distinct set.
+            private readonly HashSet<TInputOutput> _hashLookup; // The hash lookup, used to produce the distinct set.
             private readonly CancellationToken _cancellationToken;
             private Shared<int>? _outputLoopCount; // Allocated in MoveNext to avoid false sharing.
 
@@ -133,7 +133,7 @@ namespace System.Linq.Parallel
             {
                 Debug.Assert(source != null);
                 _source = source;
-                _hashLookup = new Set<TInputOutput>(comparer);
+                _hashLookup = new HashSet<TInputOutput>(comparer);
                 _cancellationToken = cancellationToken;
             }
 
index 78dbcb8..9040f66 100644 (file)
@@ -80,7 +80,6 @@
     <Compile Include="System\Linq\Select.cs" />
     <Compile Include="System\Linq\SelectMany.cs" />
     <Compile Include="System\Linq\SequenceEqual.cs" />
-    <Compile Include="System\Linq\Set.cs" />
     <Compile Include="System\Linq\Single.cs" />
     <Compile Include="System\Linq\SingleLinkedNode.cs" />
     <Compile Include="System\Linq\Skip.cs" />
index 0af3e76..2e51149 100644 (file)
@@ -9,18 +9,11 @@ namespace System.Linq
     {
         private sealed partial class DistinctIterator<TSource> : IIListProvider<TSource>
         {
-            private Set<TSource> FillSet()
-            {
-                var set = new Set<TSource>(_comparer);
-                set.UnionWith(_source);
-                return set;
-            }
+            public TSource[] ToArray() => Enumerable.HashSetToArray(new HashSet<TSource>(_source, _comparer));
 
-            public TSource[] ToArray() => FillSet().ToArray();
+            public List<TSource> ToList() => Enumerable.HashSetToList(new HashSet<TSource>(_source, _comparer));
 
-            public List<TSource> ToList() => FillSet().ToList();
-
-            public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : FillSet().Count;
+            public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : new HashSet<TSource>(_source, _comparer).Count;
         }
     }
 }
index f31e324..5c59fc8 100644 (file)
@@ -28,7 +28,7 @@ namespace System.Linq
         {
             private readonly IEnumerable<TSource> _source;
             private readonly IEqualityComparer<TSource>? _comparer;
-            private Set<TSource>? _set;
+            private HashSet<TSource>? _set;
             private IEnumerator<TSource>? _enumerator;
 
             public DistinctIterator(IEnumerable<TSource> source, IEqualityComparer<TSource>? comparer)
@@ -53,7 +53,7 @@ namespace System.Linq
                         }
 
                         TSource element = _enumerator.Current;
-                        _set = new Set<TSource>(_comparer);
+                        _set = new HashSet<TSource>(DefaultInternalSetCapacity, _comparer);
                         _set.Add(element);
                         _current = element;
                         _state = 2;
index 681cac6..b3e0f45 100644 (file)
@@ -39,8 +39,7 @@ namespace System.Linq
 
         private static IEnumerable<TSource> ExceptIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource>? comparer)
         {
-            Set<TSource> set = new Set<TSource>(comparer);
-            set.UnionWith(second);
+            var set = new HashSet<TSource>(second, comparer);
 
             foreach (TSource element in first)
             {
index ab6afc2..88c6791 100644 (file)
@@ -39,8 +39,7 @@ namespace System.Linq
 
         private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource>? comparer)
         {
-            Set<TSource> set = new Set<TSource>(comparer);
-            set.UnionWith(second);
+            var set = new HashSet<TSource>(second, comparer);
 
             foreach (TSource element in first)
             {
diff --git a/src/libraries/System.Linq/src/System/Linq/Set.cs b/src/libraries/System.Linq/src/System/Linq/Set.cs
deleted file mode 100644 (file)
index 1d12834..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-
-namespace System.Linq
-{
-    /// <summary>
-    /// A lightweight hash set.
-    /// </summary>
-    /// <typeparam name="TElement">The type of the set's items.</typeparam>
-    internal sealed class Set<TElement>
-    {
-        /// <summary>
-        /// The comparer used to hash and compare items in the set.
-        /// </summary>
-        private readonly IEqualityComparer<TElement> _comparer;
-
-        /// <summary>
-        /// The hash buckets, which are used to index into the slots.
-        /// </summary>
-        private int[] _buckets;
-
-        /// <summary>
-        /// The slots, each of which store an item and its hash code.
-        /// </summary>
-        private Slot[] _slots;
-
-        /// <summary>
-        /// The number of items in this set.
-        /// </summary>
-        private int _count;
-
-#if DEBUG
-        /// <summary>
-        /// Whether <see cref="Remove"/> has been called on this set.
-        /// </summary>
-        /// <remarks>
-        /// When <see cref="Remove"/> runs in debug builds, this flag is set to <c>true</c>.
-        /// Other methods assert that this flag is <c>false</c> in debug builds, because
-        /// they make optimizations that may not be correct if <see cref="Remove"/> is called
-        /// beforehand.
-        /// </remarks>
-        private bool _haveRemoved;
-#endif
-
-        /// <summary>
-        /// Constructs a set that compares items with the specified comparer.
-        /// </summary>
-        /// <param name="comparer">
-        /// The comparer. If this is <c>null</c>, it defaults to <see cref="EqualityComparer{TElement}.Default"/>.
-        /// </param>
-        public Set(IEqualityComparer<TElement>? comparer)
-        {
-            _comparer = comparer ?? EqualityComparer<TElement>.Default;
-            _buckets = new int[7];
-            _slots = new Slot[7];
-        }
-
-        /// <summary>
-        /// Attempts to add an item to this set.
-        /// </summary>
-        /// <param name="value">The item to add.</param>
-        /// <returns>
-        /// <c>true</c> if the item was not in the set; otherwise, <c>false</c>.
-        /// </returns>
-        public bool Add(TElement value)
-        {
-#if DEBUG
-            Debug.Assert(!_haveRemoved, "This class is optimised for never calling Add after Remove. If your changes need to do so, undo that optimization.");
-#endif
-            int hashCode = InternalGetHashCode(value);
-            for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i]._next)
-            {
-                if (_slots[i]._hashCode == hashCode && _comparer.Equals(_slots[i]._value, value))
-                {
-                    return false;
-                }
-            }
-
-            if (_count == _slots.Length)
-            {
-                Resize();
-            }
-
-            int index = _count;
-            _count++;
-            int bucket = hashCode % _buckets.Length;
-            _slots[index]._hashCode = hashCode;
-            _slots[index]._value = value;
-            _slots[index]._next = _buckets[bucket] - 1;
-            _buckets[bucket] = index + 1;
-            return true;
-        }
-
-        /// <summary>
-        /// Attempts to remove an item from this set.
-        /// </summary>
-        /// <param name="value">The item to remove.</param>
-        /// <returns>
-        /// <c>true</c> if the item was in the set; otherwise, <c>false</c>.
-        /// </returns>
-        public bool Remove(TElement value)
-        {
-#if DEBUG
-            _haveRemoved = true;
-#endif
-            int hashCode = InternalGetHashCode(value);
-            int bucket = hashCode % _buckets.Length;
-            int last = -1;
-            for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i]._next)
-            {
-                if (_slots[i]._hashCode == hashCode && _comparer.Equals(_slots[i]._value, value))
-                {
-                    if (last < 0)
-                    {
-                        _buckets[bucket] = _slots[i]._next + 1;
-                    }
-                    else
-                    {
-                        _slots[last]._next = _slots[i]._next;
-                    }
-
-                    _slots[i]._hashCode = -1;
-                    _slots[i]._value = default!;
-                    _slots[i]._next = -1;
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Expands the capacity of this set to double the current capacity, plus one.
-        /// </summary>
-        private void Resize()
-        {
-            int newSize = checked((_count * 2) + 1);
-            int[] newBuckets = new int[newSize];
-            Slot[] newSlots = new Slot[newSize];
-            Array.Copy(_slots, newSlots, _count);
-            for (int i = 0; i < _count; i++)
-            {
-                int bucket = newSlots[i]._hashCode % newSize;
-                newSlots[i]._next = newBuckets[bucket] - 1;
-                newBuckets[bucket] = i + 1;
-            }
-
-            _buckets = newBuckets;
-            _slots = newSlots;
-        }
-
-        /// <summary>
-        /// Creates an array from the items in this set.
-        /// </summary>
-        /// <returns>An array of the items in this set.</returns>
-        public TElement[] ToArray()
-        {
-#if DEBUG
-            Debug.Assert(!_haveRemoved, "Optimised ToArray cannot be called if Remove has been called.");
-#endif
-            TElement[] array = new TElement[_count];
-            for (int i = 0; i != array.Length; ++i)
-            {
-                array[i] = _slots[i]._value;
-            }
-
-            return array;
-        }
-
-        /// <summary>
-        /// Creates a list from the items in this set.
-        /// </summary>
-        /// <returns>A list of the items in this set.</returns>
-        public List<TElement> ToList()
-        {
-#if DEBUG
-            Debug.Assert(!_haveRemoved, "Optimised ToList cannot be called if Remove has been called.");
-#endif
-            int count = _count;
-            List<TElement> list = new List<TElement>(count);
-            for (int i = 0; i != count; ++i)
-            {
-                list.Add(_slots[i]._value);
-            }
-
-            return list;
-        }
-
-        /// <summary>
-        /// The number of items in this set.
-        /// </summary>
-        public int Count => _count;
-
-        /// <summary>
-        /// Unions this set with an enumerable.
-        /// </summary>
-        /// <param name="other">The enumerable.</param>
-        public void UnionWith(IEnumerable<TElement> other)
-        {
-            Debug.Assert(other != null);
-
-            foreach (TElement item in other)
-            {
-                Add(item);
-            }
-        }
-
-        /// <summary>
-        /// Gets the hash code of the provided value with its sign bit zeroed out, so that modulo has a positive result.
-        /// </summary>
-        /// <param name="value">The value to hash.</param>
-        /// <returns>The lower 31 bits of the value's hash code.</returns>
-        private int InternalGetHashCode(TElement value) => value == null ? 0 : _comparer.GetHashCode(value) & 0x7FFFFFFF;
-
-        /// <summary>
-        /// An entry in the hash set.
-        /// </summary>
-        private struct Slot
-        {
-            /// <summary>
-            /// The hash code of the item.
-            /// </summary>
-            internal int _hashCode;
-
-            /// <summary>
-            /// In the case of a hash collision, the index of the next slot to probe.
-            /// </summary>
-            internal int _next;
-
-            /// <summary>
-            /// The item held by this slot.
-            /// </summary>
-            internal TElement _value;
-        }
-    }
-}
index d066d5a..038a47e 100644 (file)
@@ -178,5 +178,28 @@ namespace System.Linq
             // Don't pre-allocate based on knowledge of size, as potentially many elements will be dropped.
             return new HashSet<TSource>(source, comparer);
         }
+
+        /// <summary>Default initial capacity to use when creating sets for internal temporary storage.</summary>
+        /// <remarks>This is based on the implicit size used in previous implementations, which used a custom Set type.</remarks>
+        private const int DefaultInternalSetCapacity = 7;
+
+        private static TSource[] HashSetToArray<TSource>(HashSet<TSource> set)
+        {
+            var result = new TSource[set.Count];
+            set.CopyTo(result);
+            return result;
+        }
+
+        private static List<TSource> HashSetToList<TSource>(HashSet<TSource> set)
+        {
+            var result = new List<TSource>(set.Count);
+
+            foreach (TSource item in set)
+            {
+                result.Add(item);
+            }
+
+            return result;
+        }
     }
 }
index eb21abe..cedfa89 100644 (file)
@@ -9,9 +9,9 @@ namespace System.Linq
     {
         private abstract partial class UnionIterator<TSource> : IIListProvider<TSource>
         {
-            private Set<TSource> FillSet()
+            private HashSet<TSource> FillSet()
             {
-                var set = new Set<TSource>(_comparer);
+                var set = new HashSet<TSource>(_comparer);
                 for (int index = 0; ; ++index)
                 {
                     IEnumerable<TSource>? enumerable = GetEnumerable(index);
@@ -24,9 +24,9 @@ namespace System.Linq
                 }
             }
 
-            public TSource[] ToArray() => FillSet().ToArray();
+            public TSource[] ToArray() => Enumerable.HashSetToArray(FillSet());
 
-            public List<TSource> ToList() => FillSet().ToList();
+            public List<TSource> ToList() => Enumerable.HashSetToList(FillSet());
 
             public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : FillSet().Count;
         }
index e5cfdb0..d9b3c4b 100644 (file)
@@ -34,7 +34,7 @@ namespace System.Linq
         {
             internal readonly IEqualityComparer<TSource>? _comparer;
             private IEnumerator<TSource>? _enumerator;
-            private Set<TSource>? _set;
+            private HashSet<TSource>? _set;
 
             protected UnionIterator(IEqualityComparer<TSource>? comparer)
             {
@@ -68,7 +68,7 @@ namespace System.Linq
             {
                 Debug.Assert(_enumerator != null);
 
-                Set<TSource> set = new Set<TSource>(_comparer);
+                var set = new HashSet<TSource>(DefaultInternalSetCapacity, _comparer);
                 TSource element = _enumerator.Current;
                 set.Add(element);
                 _current = element;
@@ -80,7 +80,7 @@ namespace System.Linq
                 Debug.Assert(_enumerator != null);
                 Debug.Assert(_set != null);
 
-                Set<TSource> set = _set;
+                HashSet<TSource> set = _set;
 
                 while (_enumerator.MoveNext())
                 {