From 30e0e5f69303c51cb787f45c8d1a791f1db51328 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 12 Mar 2021 10:21:16 -0500 Subject: [PATCH] Replace LINQ's custom Set with HashSet --- .../src/System.Linq.Parallel.csproj | 2 +- .../src/System/Linq/Parallel/Helpers.cs | 139 ---------- .../src/System/Linq/Parallel/JaggedArray.cs | 17 ++ .../Binary/ExceptQueryOperator.cs | 6 +- .../Binary/IntersectQueryOperator.cs | 4 +- .../Binary/UnionQueryOperator.cs | 4 +- .../Unary/DistinctQueryOperator.cs | 4 +- .../System.Linq/src/System.Linq.csproj | 1 - .../src/System/Linq/Distinct.SpeedOpt.cs | 13 +- .../System.Linq/src/System/Linq/Distinct.cs | 4 +- .../System.Linq/src/System/Linq/Except.cs | 3 +- .../System.Linq/src/System/Linq/Intersect.cs | 3 +- .../System.Linq/src/System/Linq/Set.cs | 240 ------------------ .../src/System/Linq/ToCollection.cs | 23 ++ .../src/System/Linq/Union.SpeedOpt.cs | 8 +- .../System.Linq/src/System/Linq/Union.cs | 6 +- 16 files changed, 64 insertions(+), 413 deletions(-) delete mode 100644 src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Helpers.cs create mode 100644 src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/JaggedArray.cs delete mode 100644 src/libraries/System.Linq/src/System/Linq/Set.cs diff --git a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj index c04ba4bf4e383..b78dd81e422a9 100644 --- a/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj +++ b/src/libraries/System.Linq.Parallel/src/System.Linq.Parallel.csproj @@ -18,6 +18,7 @@ + @@ -123,7 +124,6 @@ - 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 index 54494258c2ed7..0000000000000 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Helpers.cs +++ /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 - { - 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 - { - private int[] _buckets; - private Slot[] _slots; - private int _count; - private readonly IEqualityComparer _comparer; -#if DEBUG - private bool _haveRemoved; -#endif - - private const int InitialSize = 7; - private const int HashCodeMask = 0x7FFFFFFF; - - public Set(IEqualityComparer? comparer) - { - if (comparer == null) comparer = EqualityComparer.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 index 0000000000000..54c74e82279fb --- /dev/null +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/JaggedArray.cs @@ -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 + { + 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; + } + } +} diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/ExceptQueryOperator.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/ExceptQueryOperator.cs index 3df3f4ebb8500..5a0cd8a04ee45 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/ExceptQueryOperator.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/ExceptQueryOperator.cs @@ -139,7 +139,7 @@ private class ExceptQueryOperatorEnumerator : QueryOperatorEnumerator< private readonly QueryOperatorEnumerator, TLeftKey> _leftSource; // Left data source. private readonly QueryOperatorEnumerator, int> _rightSource; // Right data source. private readonly IEqualityComparer? _comparer; // A comparer used for equality checks/hash-coding. - private Set? _hashLookup; // The hash lookup, used to produce the distinct set. + private HashSet? _hashLookup; // The hash lookup, used to produce the distinct set. private readonly CancellationToken _cancellationToken; private Shared? _outputLoopCount; @@ -177,7 +177,7 @@ internal override bool MoveNext([MaybeNullWhen(false), AllowNull] ref TInputOutp { _outputLoopCount = new Shared(0); - _hashLookup = new Set(_comparer); + _hashLookup = new HashSet(_comparer); Pair rightElement = default(Pair); int rightKeyUnused = default(int); @@ -265,7 +265,7 @@ internal override bool MoveNext([MaybeNullWhen(false), AllowNull] ref TInputOutp // Build the set out of the left data source, if we haven't already. if (_outputEnumerator == null) { - Set rightLookup = new Set(_comparer); + HashSet rightLookup = new HashSet(_comparer); Pair rightElement = default(Pair); int rightKeyUnused = default(int); diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/IntersectQueryOperator.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/IntersectQueryOperator.cs index 3b6230f441992..9954272ad4694 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/IntersectQueryOperator.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/IntersectQueryOperator.cs @@ -128,7 +128,7 @@ private class IntersectQueryOperatorEnumerator : QueryOperatorEnumerat private readonly QueryOperatorEnumerator, TLeftKey> _leftSource; // Left data source. private readonly QueryOperatorEnumerator, int> _rightSource; // Right data source. private readonly IEqualityComparer? _comparer; // Comparer to use for equality/hash-coding. - private Set? _hashLookup; // The hash lookup, used to produce the intersection. + private HashSet? _hashLookup; // The hash lookup, used to produce the intersection. private readonly CancellationToken _cancellationToken; private Shared? _outputLoopCount; @@ -164,7 +164,7 @@ internal override bool MoveNext([MaybeNullWhen(false), AllowNull] ref TInputOutp if (_hashLookup == null) { _outputLoopCount = new Shared(0); - _hashLookup = new Set(_comparer); + _hashLookup = new HashSet(_comparer); Pair rightElement = default(Pair); int rightKeyUnused = default(int); diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/UnionQueryOperator.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/UnionQueryOperator.cs index 1d6cccfcef6e9..06aabb4a7b215 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/UnionQueryOperator.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Binary/UnionQueryOperator.cs @@ -184,7 +184,7 @@ private class UnionQueryOperatorEnumerator : QueryOperatorE { private QueryOperatorEnumerator, TLeftKey>? _leftSource; // Left data source. private QueryOperatorEnumerator, TRightKey>? _rightSource; // Right data source. - private Set? _hashLookup; // The hash lookup, used to produce the union. + private HashSet? _hashLookup; // The hash lookup, used to produce the union. private readonly CancellationToken _cancellationToken; private Shared? _outputLoopCount; private readonly IEqualityComparer? _comparer; @@ -216,7 +216,7 @@ internal override bool MoveNext([MaybeNullWhen(false), AllowNull] ref TInputOutp { if (_hashLookup == null) { - _hashLookup = new Set(_comparer); + _hashLookup = new HashSet(_comparer); _outputLoopCount = new Shared(0); } diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Unary/DistinctQueryOperator.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Unary/DistinctQueryOperator.cs index 4608cecd5c2e5..2621cdb740a15 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Unary/DistinctQueryOperator.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/QueryOperators/Unary/DistinctQueryOperator.cs @@ -119,7 +119,7 @@ internal override bool LimitsParallelism private class DistinctQueryOperatorEnumerator : QueryOperatorEnumerator { private readonly QueryOperatorEnumerator, TKey> _source; // The data source. - private readonly Set _hashLookup; // The hash lookup, used to produce the distinct set. + private readonly HashSet _hashLookup; // The hash lookup, used to produce the distinct set. private readonly CancellationToken _cancellationToken; private Shared? _outputLoopCount; // Allocated in MoveNext to avoid false sharing. @@ -133,7 +133,7 @@ internal DistinctQueryOperatorEnumerator( { Debug.Assert(source != null); _source = source; - _hashLookup = new Set(comparer); + _hashLookup = new HashSet(comparer); _cancellationToken = cancellationToken; } diff --git a/src/libraries/System.Linq/src/System.Linq.csproj b/src/libraries/System.Linq/src/System.Linq.csproj index 78dbcb88cfe2f..9040f668ab6f1 100644 --- a/src/libraries/System.Linq/src/System.Linq.csproj +++ b/src/libraries/System.Linq/src/System.Linq.csproj @@ -80,7 +80,6 @@ - diff --git a/src/libraries/System.Linq/src/System/Linq/Distinct.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Distinct.SpeedOpt.cs index 0af3e76984b12..454b499905fd3 100644 --- a/src/libraries/System.Linq/src/System/Linq/Distinct.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Distinct.SpeedOpt.cs @@ -9,18 +9,11 @@ public static partial class Enumerable { private sealed partial class DistinctIterator : IIListProvider { - private Set FillSet() - { - var set = new Set(_comparer); - set.UnionWith(_source); - return set; - } + public TSource[] ToArray() => Enumerable.ToArray(new HashSet(_source, _comparer)); - public TSource[] ToArray() => FillSet().ToArray(); + public List ToList() => Enumerable.ToList(new HashSet(_source, _comparer)); - public List ToList() => FillSet().ToList(); - - public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : FillSet().Count; + public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : new HashSet(_source, _comparer).Count; } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Distinct.cs b/src/libraries/System.Linq/src/System/Linq/Distinct.cs index f31e324e0fbad..5c59fc8164d32 100644 --- a/src/libraries/System.Linq/src/System/Linq/Distinct.cs +++ b/src/libraries/System.Linq/src/System/Linq/Distinct.cs @@ -28,7 +28,7 @@ private sealed partial class DistinctIterator : Iterator { private readonly IEnumerable _source; private readonly IEqualityComparer? _comparer; - private Set? _set; + private HashSet? _set; private IEnumerator? _enumerator; public DistinctIterator(IEnumerable source, IEqualityComparer? comparer) @@ -53,7 +53,7 @@ public override bool MoveNext() } TSource element = _enumerator.Current; - _set = new Set(_comparer); + _set = new HashSet(DefaultInternalSetCapacity, _comparer); _set.Add(element); _current = element; _state = 2; diff --git a/src/libraries/System.Linq/src/System/Linq/Except.cs b/src/libraries/System.Linq/src/System/Linq/Except.cs index 681cac6e1a32e..b3e0f45075d2d 100644 --- a/src/libraries/System.Linq/src/System/Linq/Except.cs +++ b/src/libraries/System.Linq/src/System/Linq/Except.cs @@ -39,8 +39,7 @@ public static IEnumerable Except(this IEnumerable fir private static IEnumerable ExceptIterator(IEnumerable first, IEnumerable second, IEqualityComparer? comparer) { - Set set = new Set(comparer); - set.UnionWith(second); + var set = new HashSet(second, comparer); foreach (TSource element in first) { diff --git a/src/libraries/System.Linq/src/System/Linq/Intersect.cs b/src/libraries/System.Linq/src/System/Linq/Intersect.cs index ab6afc24c3297..88c679106aba2 100644 --- a/src/libraries/System.Linq/src/System/Linq/Intersect.cs +++ b/src/libraries/System.Linq/src/System/Linq/Intersect.cs @@ -39,8 +39,7 @@ public static IEnumerable Intersect(this IEnumerable private static IEnumerable IntersectIterator(IEnumerable first, IEnumerable second, IEqualityComparer? comparer) { - Set set = new Set(comparer); - set.UnionWith(second); + var set = new HashSet(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 index 1d12834bc8087..0000000000000 --- a/src/libraries/System.Linq/src/System/Linq/Set.cs +++ /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 -{ - /// - /// A lightweight hash set. - /// - /// The type of the set's items. - internal sealed class Set - { - /// - /// The comparer used to hash and compare items in the set. - /// - private readonly IEqualityComparer _comparer; - - /// - /// The hash buckets, which are used to index into the slots. - /// - private int[] _buckets; - - /// - /// The slots, each of which store an item and its hash code. - /// - private Slot[] _slots; - - /// - /// The number of items in this set. - /// - private int _count; - -#if DEBUG - /// - /// Whether has been called on this set. - /// - /// - /// When runs in debug builds, this flag is set to true. - /// Other methods assert that this flag is false in debug builds, because - /// they make optimizations that may not be correct if is called - /// beforehand. - /// - private bool _haveRemoved; -#endif - - /// - /// Constructs a set that compares items with the specified comparer. - /// - /// - /// The comparer. If this is null, it defaults to . - /// - public Set(IEqualityComparer? comparer) - { - _comparer = comparer ?? EqualityComparer.Default; - _buckets = new int[7]; - _slots = new Slot[7]; - } - - /// - /// Attempts to add an item to this set. - /// - /// The item to add. - /// - /// true if the item was not in the set; otherwise, 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 - 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; - } - - /// - /// Attempts to remove an item from this set. - /// - /// The item to remove. - /// - /// true if the item was in the set; otherwise, 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; - } - - /// - /// Expands the capacity of this set to double the current capacity, plus one. - /// - 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; - } - - /// - /// Creates an array from the items in this set. - /// - /// An array of the items in this set. - 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; - } - - /// - /// Creates a list from the items in this set. - /// - /// A list of the items in this set. - public List ToList() - { -#if DEBUG - Debug.Assert(!_haveRemoved, "Optimised ToList cannot be called if Remove has been called."); -#endif - int count = _count; - List list = new List(count); - for (int i = 0; i != count; ++i) - { - list.Add(_slots[i]._value); - } - - return list; - } - - /// - /// The number of items in this set. - /// - public int Count => _count; - - /// - /// Unions this set with an enumerable. - /// - /// The enumerable. - public void UnionWith(IEnumerable other) - { - Debug.Assert(other != null); - - foreach (TElement item in other) - { - Add(item); - } - } - - /// - /// Gets the hash code of the provided value with its sign bit zeroed out, so that modulo has a positive result. - /// - /// The value to hash. - /// The lower 31 bits of the value's hash code. - private int InternalGetHashCode(TElement value) => value == null ? 0 : _comparer.GetHashCode(value) & 0x7FFFFFFF; - - /// - /// An entry in the hash set. - /// - private struct Slot - { - /// - /// The hash code of the item. - /// - internal int _hashCode; - - /// - /// In the case of a hash collision, the index of the next slot to probe. - /// - internal int _next; - - /// - /// The item held by this slot. - /// - internal TElement _value; - } - } -} diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index d066d5a270442..a4d4a31ad4120 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -178,5 +178,28 @@ public static HashSet ToHashSet(this IEnumerable sour // Don't pre-allocate based on knowledge of size, as potentially many elements will be dropped. return new HashSet(source, comparer); } + + /// Default initial capacity to use when creating sets for internal temporary storage. + /// This is based on the implicit size used in previous implementations, which used a custom Set type. + private const int DefaultInternalSetCapacity = 7; + + private static TSource[] ToArray(HashSet set) + { + var result = new TSource[set.Count]; + set.CopyTo(result); + return result; + } + + private static List ToList(HashSet set) + { + var result = new List(set.Count); + + foreach (TSource item in set) + { + result.Add(item); + } + + return result; + } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Union.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Union.SpeedOpt.cs index eb21abe7dd14b..2aff76d831252 100644 --- a/src/libraries/System.Linq/src/System/Linq/Union.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Union.SpeedOpt.cs @@ -9,9 +9,9 @@ public static partial class Enumerable { private abstract partial class UnionIterator : IIListProvider { - private Set FillSet() + private HashSet FillSet() { - var set = new Set(_comparer); + var set = new HashSet(_comparer); for (int index = 0; ; ++index) { IEnumerable? enumerable = GetEnumerable(index); @@ -24,9 +24,9 @@ private Set FillSet() } } - public TSource[] ToArray() => FillSet().ToArray(); + public TSource[] ToArray() => Enumerable.ToArray(FillSet()); - public List ToList() => FillSet().ToList(); + public List ToList() => Enumerable.ToList(FillSet()); public int GetCount(bool onlyIfCheap) => onlyIfCheap ? -1 : FillSet().Count; } diff --git a/src/libraries/System.Linq/src/System/Linq/Union.cs b/src/libraries/System.Linq/src/System/Linq/Union.cs index e5cfdb0462c72..d9b3c4bdb065b 100644 --- a/src/libraries/System.Linq/src/System/Linq/Union.cs +++ b/src/libraries/System.Linq/src/System/Linq/Union.cs @@ -34,7 +34,7 @@ private abstract partial class UnionIterator : Iterator { internal readonly IEqualityComparer? _comparer; private IEnumerator? _enumerator; - private Set? _set; + private HashSet? _set; protected UnionIterator(IEqualityComparer? comparer) { @@ -68,7 +68,7 @@ private void StoreFirst() { Debug.Assert(_enumerator != null); - Set set = new Set(_comparer); + var set = new HashSet(DefaultInternalSetCapacity, _comparer); TSource element = _enumerator.Current; set.Add(element); _current = element; @@ -80,7 +80,7 @@ private bool GetNext() Debug.Assert(_enumerator != null); Debug.Assert(_set != null); - Set set = _set; + HashSet set = _set; while (_enumerator.MoveNext()) {