diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index c890bbed83c7ef..063db8233d7978 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -17,14 +17,17 @@ namespace System.Collections.Generic [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public class Dictionary : IDictionary, IDictionary, IReadOnlyDictionary, ISerializable, IDeserializationCallback where TKey : notnull { + private static int[]? EmptyBuckets; + private static Entry[]? EmptyEntries; + // constants for serialization private const string VersionName = "Version"; // Do not rename (binary serialization) private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization) private const string ComparerName = "Comparer"; // Do not rename (binary serialization) - private int[]? _buckets; - private Entry[]? _entries; + private int[] _buckets; + private Entry[] _entries; #if TARGET_64BIT private ulong _fastModMultiplier; #endif @@ -37,6 +40,14 @@ public class Dictionary : IDictionary, IDictionary, private ValueCollection? _values; private const int StartOfFreeList = -3; + private static int[] GetEmptyBuckets () => + EmptyBuckets ??= new int[1]; + +#pragma warning disable CA1825 // avoid the extra generic instantiation for Array.Empty() + private static Entry[] GetEmptyEntries () => + EmptyEntries ??= new Entry[0]; +#pragma warning restore CA1825 + public Dictionary() : this(0, null) { } public Dictionary(int capacity) : this(capacity, null) { } @@ -50,6 +61,10 @@ public Dictionary(int capacity, IEqualityComparer? comparer) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); } + // HACK: Satisfy nullability analyzer + _buckets = GetEmptyBuckets(); + _entries = GetEmptyEntries(); + if (capacity > 0) { Initialize(capacity); @@ -126,9 +141,7 @@ private void AddRange(IEnumerable> enumerable) // This is not currently a true .AddRange as it needs to be an initialized dictionary // of the correct size, and also an empty dictionary with no current entities (and no argument checks). - Debug.Assert(source._entries is not null); - Debug.Assert(_entries is not null); - Debug.Assert(_entries.Length >= source.Count); + Debug.Assert(Capacity >= source.Count); Debug.Assert(_count == 0); Entry[] oldEntries = source._entries; @@ -184,6 +197,9 @@ private void AddRange(IEnumerable> enumerable) [EditorBrowsable(EditorBrowsableState.Never)] protected Dictionary(SerializationInfo info, StreamingContext context) { + _buckets = GetEmptyBuckets(); + _entries = GetEmptyEntries(); + // We can't do anything with the keys and values until the entire graph has been deserialized // and we have a resonable estimate that GetHashCode is not going to fail. For the time being, // we'll just cache this. The graph is not valid until OnDeserialization has been called. @@ -208,10 +224,18 @@ public IEqualityComparer Comparer public int Count => _count - _freeCount; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsEmptyArray (int[] buckets) => + (buckets == EmptyBuckets); + /// /// Gets the total numbers of elements the internal data structure can hold without resizing. /// - public int Capacity => _entries?.Length ?? 0; + public int Capacity { + get { + return _entries.Length; + } + } public KeyCollection Keys => _keys ??= new KeyCollection(this); @@ -282,9 +306,6 @@ public void Clear() int count = _count; if (count > 0) { - Debug.Assert(_buckets != null, "_buckets should be non-null"); - Debug.Assert(_entries != null, "_entries should be non-null"); - Array.Clear(_buckets); _count = 0; @@ -299,12 +320,12 @@ public bool ContainsKey(TKey key) => public bool ContainsValue(TValue value) { - Entry[]? entries = _entries; + Entry[] entries = _entries; if (value == null) { for (int i = 0; i < _count; i++) { - if (entries![i].next >= -1 && entries[i].value == null) + if (entries[i].next >= -1 && entries[i].value == null) { return true; } @@ -315,7 +336,7 @@ public bool ContainsValue(TValue value) // ValueType: Devirtualize with EqualityComparer.Default intrinsic for (int i = 0; i < _count; i++) { - if (entries![i].next >= -1 && EqualityComparer.Default.Equals(entries[i].value, value)) + if (entries[i].next >= -1 && EqualityComparer.Default.Equals(entries[i].value, value)) { return true; } @@ -329,7 +350,7 @@ public bool ContainsValue(TValue value) EqualityComparer defaultComparer = EqualityComparer.Default; for (int i = 0; i < _count; i++) { - if (entries![i].next >= -1 && defaultComparer.Equals(entries[i].value, value)) + if (entries[i].next >= -1 && defaultComparer.Equals(entries[i].value, value)) { return true; } @@ -357,10 +378,10 @@ private void CopyTo(KeyValuePair[] array, int index) } int count = _count; - Entry[]? entries = _entries; + Entry[] entries = _entries; for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) + if (entries[i].next >= -1) { array[index++] = new KeyValuePair(entries[i].key, entries[i].value); } @@ -384,9 +405,9 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte info.AddValue(VersionName, _version); info.AddValue(ComparerName, Comparer, typeof(IEqualityComparer)); - info.AddValue(HashSizeName, _buckets == null ? 0 : _buckets.Length); // This is the length of the bucket array + info.AddValue(HashSizeName, IsEmptyArray(_buckets) ? 0 : _buckets.Length); // This is the length of the bucket array - if (_buckets != null) + if (!IsEmptyArray(_buckets)) { var array = new KeyValuePair[Count]; CopyTo(array, 0); @@ -402,78 +423,72 @@ internal ref TValue FindValue(TKey key) } ref Entry entry = ref Unsafe.NullRef(); - if (_buckets != null) + IEqualityComparer? comparer = _comparer; + if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types + comparer == null) { - Debug.Assert(_entries != null, "expected entries to be != null"); - IEqualityComparer? comparer = _comparer; - if (typeof(TKey).IsValueType && // comparer can only be null for value types; enable JIT to eliminate entire if block for ref types - comparer == null) - { - uint hashCode = (uint)key.GetHashCode(); - int i = GetBucket(hashCode); - Entry[]? entries = _entries; - uint collisionCount = 0; + uint hashCode = (uint)key.GetHashCode(); + int i = GetBucket(hashCode); + Entry[] entries = _entries; + uint collisionCount = 0; - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. - do + // ValueType: Devirtualize with EqualityComparer.Default intrinsic + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do + { + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - goto ReturnNotFound; - } + goto ReturnNotFound; + } - entry = ref entries[i]; - if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) - { - goto ReturnFound; - } + entry = ref entries[i]; + if (entry.hashCode == hashCode && EqualityComparer.Default.Equals(entry.key, key)) + { + goto ReturnFound; + } - i = entry.next; + i = entry.next; - collisionCount++; - } while (collisionCount <= (uint)entries.Length); + collisionCount++; + } while (collisionCount <= (uint)entries.Length); - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - goto ConcurrentOperation; - } - else + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + goto ConcurrentOperation; + } + else + { + Debug.Assert(comparer is not null); + uint hashCode = (uint)comparer.GetHashCode(key); + int i = GetBucket(hashCode); + Entry[] entries = _entries; + uint collisionCount = 0; + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do { - Debug.Assert(comparer is not null); - uint hashCode = (uint)comparer.GetHashCode(key); - int i = GetBucket(hashCode); - Entry[]? entries = _entries; - uint collisionCount = 0; - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. - do + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - goto ReturnNotFound; - } + goto ReturnNotFound; + } - entry = ref entries[i]; - if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) - { - goto ReturnFound; - } + entry = ref entries[i]; + if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) + { + goto ReturnFound; + } - i = entry.next; + i = entry.next; - collisionCount++; - } while (collisionCount <= (uint)entries.Length); + collisionCount++; + } while (collisionCount <= (uint)entries.Length); - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - goto ConcurrentOperation; - } + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + goto ConcurrentOperation; } - goto ReturnNotFound; - ConcurrentOperation: ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); ReturnFound: @@ -487,21 +502,48 @@ internal ref TValue FindValue(TKey key) private int Initialize(int capacity) { - int size = HashHelpers.GetPrime(capacity); - int[] buckets = new int[size]; - Entry[] entries = new Entry[size]; - - // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails - _freeList = -1; -#if TARGET_64BIT - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size); -#endif - _buckets = buckets; - _entries = entries; + int size; + if (capacity == 0) { + size = 0; + _freeList = -1; + #if TARGET_64BIT + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)1); + #endif + _buckets = GetEmptyBuckets(); + _entries = GetEmptyEntries(); + } else { + size = HashHelpers.GetPrime(capacity); + int[] buckets = new int[size]; + Entry[] entries = new Entry[size]; + + // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails + _freeList = -1; + #if TARGET_64BIT + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size); + #endif + _buckets = buckets; + _entries = entries; + } return size; } + /// + /// Fetches the dictionary's entries array, and if it is the Empty array, resizes the dictionary + /// to have space for at least one item to prepare it for insertion or modification. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Entry[] GetMutableEntries() + { + Entry[] entries = _entries; + if (entries.Length < 1) + { + Initialize(1); + return _entries; + } + return entries; + } + private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) { // NOTE: this method is mirrored in CollectionsMarshal.GetValueRefOrAddDefault below. @@ -512,14 +554,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (_buckets == null) - { - Initialize(0); - } - Debug.Assert(_buckets != null); - - Entry[]? entries = _entries; - Debug.Assert(entries != null, "expected entries to be non-null"); + Entry[] entries = GetMutableEntries(); IEqualityComparer? comparer = _comparer; Debug.Assert(comparer is not null || typeof(TKey).IsValueType); @@ -616,7 +651,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) entries = _entries; } - ref Entry entry = ref entries![index]; + ref Entry entry = ref entries[index]; entry.hashCode = hashCode; entry.next = bucket - 1; // Value in _buckets is 1-based entry.key = key; @@ -803,41 +838,34 @@ internal ref TValue FindValue(TAlternateKey key, [MaybeNullWhen(false)] out TKey IAlternateEqualityComparer comparer = GetAlternateComparer(dictionary); ref Entry entry = ref Unsafe.NullRef(); - if (dictionary._buckets != null) + uint hashCode = (uint)comparer.GetHashCode(key); + int i = dictionary.GetBucket(hashCode); + Entry[] entries = dictionary._entries; + uint collisionCount = 0; + i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + do { - Debug.Assert(dictionary._entries != null, "expected entries to be != null"); - - uint hashCode = (uint)comparer.GetHashCode(key); - int i = dictionary.GetBucket(hashCode); - Entry[]? entries = dictionary._entries; - uint collisionCount = 0; - i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. - do + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Length) { - // Should be a while loop https://github.com/dotnet/runtime/issues/9422 - // Test in if to drop range check for following array access - if ((uint)i >= (uint)entries.Length) - { - goto ReturnNotFound; - } - - entry = ref entries[i]; - if (entry.hashCode == hashCode && comparer.Equals(key, entry.key)) - { - goto ReturnFound; - } + goto ReturnNotFound; + } - i = entry.next; + entry = ref entries[i]; + if (entry.hashCode == hashCode && comparer.Equals(key, entry.key)) + { + goto ReturnFound; + } - collisionCount++; - } while (collisionCount <= (uint)entries.Length); + i = entry.next; - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - goto ConcurrentOperation; - } + collisionCount++; + } while (collisionCount <= (uint)entries.Length); - goto ReturnNotFound; + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + goto ConcurrentOperation; ConcurrentOperation: ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); @@ -873,63 +901,65 @@ public bool Remove(TAlternateKey key, [MaybeNullWhen(false)] out TKey actualKey, Dictionary dictionary = Dictionary; IAlternateEqualityComparer comparer = GetAlternateComparer(dictionary); - if (dictionary._buckets != null) - { - Debug.Assert(dictionary._entries != null, "entries should be non-null"); - uint collisionCount = 0; + if (dictionary._count == 0) { + actualKey = default; + value = default; + return false; + } - uint hashCode = (uint)comparer.GetHashCode(key); + uint collisionCount = 0; - ref int bucket = ref dictionary.GetBucket(hashCode); - Entry[]? entries = dictionary._entries; - int last = -1; - int i = bucket - 1; // Value in buckets is 1-based - while (i >= 0) - { - ref Entry entry = ref entries[i]; + uint hashCode = (uint)comparer.GetHashCode(key); + + ref int bucket = ref dictionary.GetBucket(hashCode); + Entry[] entries = dictionary._entries; + int last = -1; + int i = bucket - 1; // Value in buckets is 1-based + while (i >= 0) + { + ref Entry entry = ref entries[i]; - if (entry.hashCode == hashCode && comparer.Equals(key, entry.key)) + if (entry.hashCode == hashCode && comparer.Equals(key, entry.key)) + { + if (last < 0) { - if (last < 0) - { - bucket = entry.next + 1; // Value in buckets is 1-based - } - else - { - entries[last].next = entry.next; - } - - actualKey = entry.key; - value = entry.value; - - Debug.Assert((StartOfFreeList - dictionary._freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); - entry.next = StartOfFreeList - dictionary._freeList; - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default!; - } - - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default!; - } - - dictionary._freeList = i; - dictionary._freeCount++; - return true; + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; } - last = i; - i = entry.next; + actualKey = entry.key; + value = entry.value; - collisionCount++; - if (collisionCount > (uint)entries.Length) + Debug.Assert((StartOfFreeList - dictionary._freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - dictionary._freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + entry.key = default!; + } + + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.value = default!; } + + dictionary._freeList = i; + dictionary._freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } @@ -963,14 +993,7 @@ public bool TryAdd(TAlternateKey key, TValue value) Dictionary dictionary = Dictionary; IAlternateEqualityComparer comparer = GetAlternateComparer(dictionary); - if (dictionary._buckets == null) - { - dictionary.Initialize(0); - } - Debug.Assert(dictionary._buckets != null); - - Entry[]? entries = dictionary._entries; - Debug.Assert(entries != null, "expected entries to be non-null"); + Entry[] entries = dictionary.GetMutableEntries(); uint hashCode = (uint)comparer.GetHashCode(key); @@ -1026,7 +1049,7 @@ public bool TryAdd(TAlternateKey key, TValue value) entries = dictionary._entries; } - ref Entry entry = ref entries![index]; + ref Entry entry = ref entries[index]; entry.hashCode = hashCode; entry.next = bucket - 1; // Value in _buckets is 1-based entry.key = actualKey; @@ -1077,14 +1100,7 @@ internal static class CollectionsMarshalHelper ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (dictionary._buckets == null) - { - dictionary.Initialize(0); - } - Debug.Assert(dictionary._buckets != null); - - Entry[]? entries = dictionary._entries; - Debug.Assert(entries != null, "expected entries to be non-null"); + Entry[] entries = dictionary.GetMutableEntries(); IEqualityComparer? comparer = dictionary._comparer; Debug.Assert(comparer is not null || typeof(TKey).IsValueType); @@ -1163,7 +1179,7 @@ internal static class CollectionsMarshalHelper entries = dictionary._entries; } - ref Entry entry = ref entries![index]; + ref Entry entry = ref entries[index]; entry.hashCode = hashCode; entry.next = bucket - 1; // Value in _buckets is 1-based entry.key = key; @@ -1235,7 +1251,8 @@ public virtual void OnDeserialization(object? sender) } else { - _buckets = null; + _buckets = GetEmptyBuckets(); + _entries = GetEmptyEntries(); } _version = realVersion; @@ -1248,7 +1265,6 @@ private void Resize(int newSize, bool forceNewHashCodes) { // Value types never rehash Debug.Assert(!forceNewHashCodes || !typeof(TKey).IsValueType); - Debug.Assert(_entries != null, "_entries should be non-null"); Debug.Assert(newSize >= _entries.Length); Entry[] entries = new Entry[newSize]; @@ -1299,63 +1315,64 @@ public bool Remove(TKey key) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (_buckets != null) + if (_count == 0) { - Debug.Assert(_entries != null, "entries should be non-null"); - uint collisionCount = 0; - - IEqualityComparer? comparer = _comparer; - Debug.Assert(typeof(TKey).IsValueType || comparer is not null); - uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); + return false; + } - ref int bucket = ref GetBucket(hashCode); - Entry[]? entries = _entries; - int last = -1; - int i = bucket - 1; // Value in buckets is 1-based - while (i >= 0) - { - ref Entry entry = ref entries[i]; + uint collisionCount = 0; - if (entry.hashCode == hashCode && - (typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry.key, key) : comparer!.Equals(entry.key, key))) - { - if (last < 0) - { - bucket = entry.next + 1; // Value in buckets is 1-based - } - else - { - entries[last].next = entry.next; - } + IEqualityComparer? comparer = _comparer; + Debug.Assert(typeof(TKey).IsValueType || comparer is not null); + uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); - Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); - entry.next = StartOfFreeList - _freeList; + ref int bucket = ref GetBucket(hashCode); + Entry[] entries = _entries; + int last = -1; + int i = bucket - 1; // Value in buckets is 1-based + while (i >= 0) + { + ref Entry entry = ref entries[i]; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default!; - } + if (entry.hashCode == hashCode && + (typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry.key, key) : comparer!.Equals(entry.key, key))) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default!; - } + Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; - _freeList = i; - _freeCount++; - return true; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default!; } - last = i; - i = entry.next; - - collisionCount++; - if (collisionCount > (uint)entries.Length) + if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + entry.value = default!; } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } return false; @@ -1372,65 +1389,67 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } - if (_buckets != null) + if (_count == 0) { - Debug.Assert(_entries != null, "entries should be non-null"); - uint collisionCount = 0; - - IEqualityComparer? comparer = _comparer; - Debug.Assert(typeof(TKey).IsValueType || comparer is not null); - uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); + value = default; + return false; + } - ref int bucket = ref GetBucket(hashCode); - Entry[]? entries = _entries; - int last = -1; - int i = bucket - 1; // Value in buckets is 1-based - while (i >= 0) - { - ref Entry entry = ref entries[i]; + uint collisionCount = 0; - if (entry.hashCode == hashCode && - (typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry.key, key) : comparer!.Equals(entry.key, key))) - { - if (last < 0) - { - bucket = entry.next + 1; // Value in buckets is 1-based - } - else - { - entries[last].next = entry.next; - } + IEqualityComparer? comparer = _comparer; + Debug.Assert(typeof(TKey).IsValueType || comparer is not null); + uint hashCode = (uint)(typeof(TKey).IsValueType && comparer == null ? key.GetHashCode() : comparer!.GetHashCode(key)); - value = entry.value; + ref int bucket = ref GetBucket(hashCode); + Entry[] entries = _entries; + int last = -1; + int i = bucket - 1; // Value in buckets is 1-based + while (i >= 0) + { + ref Entry entry = ref entries[i]; - Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); - entry.next = StartOfFreeList - _freeList; + if (entry.hashCode == hashCode && + (typeof(TKey).IsValueType && comparer == null ? EqualityComparer.Default.Equals(entry.key, key) : comparer!.Equals(entry.key, key))) + { + if (last < 0) + { + bucket = entry.next + 1; // Value in buckets is 1-based + } + else + { + entries[last].next = entry.next; + } - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.key = default!; - } + value = entry.value; - if (RuntimeHelpers.IsReferenceOrContainsReferences()) - { - entry.value = default!; - } + Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry.next = StartOfFreeList - _freeList; - _freeList = i; - _freeCount++; - return true; + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + entry.key = default!; } - last = i; - i = entry.next; - - collisionCount++; - if (collisionCount > (uint)entries.Length) + if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); + entry.value = default!; } + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry.next; + + collisionCount++; + if (collisionCount > (uint)entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported(); } } @@ -1492,10 +1511,10 @@ void ICollection.CopyTo(Array array, int index) } else if (array is DictionaryEntry[] dictEntryArray) { - Entry[]? entries = _entries; + Entry[] entries = _entries; for (int i = 0; i < _count; i++) { - if (entries![i].next >= -1) + if (entries[i].next >= -1) { dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); } @@ -1512,10 +1531,10 @@ void ICollection.CopyTo(Array array, int index) try { int count = _count; - Entry[]? entries = _entries; + Entry[] entries = _entries; for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) + if (entries[i].next >= -1) { objects[index++] = new KeyValuePair(entries[i].key, entries[i].value); } @@ -1540,7 +1559,8 @@ public int EnsureCapacity(int capacity) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); } - int currentCapacity = _entries == null ? 0 : _entries.Length; + Entry[] entries = _entries; + int currentCapacity = entries.Length; if (currentCapacity >= capacity) { return currentCapacity; @@ -1548,7 +1568,7 @@ public int EnsureCapacity(int capacity) _version++; - if (_buckets == null) + if (entries.Length == 0) { return Initialize(capacity); } @@ -1588,8 +1608,8 @@ public void TrimExcess(int capacity) } int newSize = HashHelpers.GetPrime(capacity); - Entry[]? oldEntries = _entries; - int currentCapacity = oldEntries == null ? 0 : oldEntries.Length; + Entry[] oldEntries = _entries; + int currentCapacity = oldEntries.Length; if (newSize >= currentCapacity) { return; @@ -1599,15 +1619,11 @@ public void TrimExcess(int capacity) _version++; Initialize(newSize); - Debug.Assert(oldEntries is not null); - CopyEntries(oldEntries, oldCount); } private void CopyEntries(Entry[] entries, int count) { - Debug.Assert(_entries is not null); - Entry[] newEntries = _entries; int newCount = 0; for (int i = 0; i < count; i++) @@ -1741,7 +1757,7 @@ void IDictionary.Remove(object key) [MethodImpl(MethodImplOptions.AggressiveInlining)] private ref int GetBucket(uint hashCode) { - int[] buckets = _buckets!; + int[] buckets = _buckets; #if TARGET_64BIT return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; #else @@ -1793,7 +1809,7 @@ public bool MoveNext() // dictionary.count+1 could be negative if dictionary.count is int.MaxValue while ((uint)_index < (uint)_dictionary._count) { - ref Entry entry = ref _dictionary._entries![_index++]; + ref Entry entry = ref _dictionary._entries[_index++]; if (entry.next >= -1) { @@ -1916,10 +1932,10 @@ public void CopyTo(TKey[] array, int index) } int count = _dictionary._count; - Entry[]? entries = _dictionary._entries; + Entry[] entries = _dictionary._entries; for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) array[index++] = entries[i].key; + if (entries[i].next >= -1) array[index++] = entries[i].key; } } @@ -1988,12 +2004,12 @@ void ICollection.CopyTo(Array array, int index) } int count = _dictionary._count; - Entry[]? entries = _dictionary._entries; + Entry[] entries = _dictionary._entries; try { for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) objects[index++] = entries[i].key; + if (entries[i].next >= -1) objects[index++] = entries[i].key; } } catch (ArrayTypeMismatchException) @@ -2033,7 +2049,7 @@ public bool MoveNext() while ((uint)_index < (uint)_dictionary._count) { - ref Entry entry = ref _dictionary._entries![_index++]; + ref Entry entry = ref _dictionary._entries[_index++]; if (entry.next >= -1) { @@ -2111,10 +2127,10 @@ public void CopyTo(TValue[] array, int index) } int count = _dictionary._count; - Entry[]? entries = _dictionary._entries; + Entry[] entries = _dictionary._entries; for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) array[index++] = entries[i].value; + if (entries[i].next >= -1) array[index++] = entries[i].value; } } @@ -2182,12 +2198,12 @@ void ICollection.CopyTo(Array array, int index) } int count = _dictionary._count; - Entry[]? entries = _dictionary._entries; + Entry[] entries = _dictionary._entries; try { for (int i = 0; i < count; i++) { - if (entries![i].next >= -1) objects[index++] = entries[i].value!; + if (entries[i].next >= -1) objects[index++] = entries[i].value!; } } catch (ArrayTypeMismatchException) @@ -2227,7 +2243,7 @@ public bool MoveNext() while ((uint)_index < (uint)_dictionary._count) { - ref Entry entry = ref _dictionary._entries![_index++]; + ref Entry entry = ref _dictionary._entries[_index++]; if (entry.next >= -1) {