File mono-6.12-dictionary-fix.diff of Package mono
diff --git a/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs b/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs
index c79654ea40..68f3dfb6f5 100644
--- a/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs
+++ b/external/corefx/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs
@@ -3,9 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
-using System.Threading;
+
+using Internal.Runtime.CompilerServices;
namespace System.Collections.Generic
{
@@ -34,14 +36,17 @@ namespace System.Collections.Generic
[DebuggerDisplay("Count = {Count}")]
[Serializable]
#if !MONO
- [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
#endif
- public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback
+ public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback where TKey : notnull
{
private struct Entry
{
- public int hashCode; // Lower 31 bits of hash code, -1 if unused
- public int next; // Index of next entry, -1 if last
+ public uint hashCode;
+ // 0-based index of next entry in chain: -1 means end of chain
+ // also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
+ // so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
+ public int next;
public TKey key; // Key of entry
public TValue value; // Value of entry
}
@@ -55,7 +60,7 @@ namespace System.Collections.Generic
private IEqualityComparer<TKey> _comparer;
private KeyCollection _keys;
private ValueCollection _values;
- private object _syncRoot;
+ private const int StartOfFreeList = -3;
// constants for serialization
private const string VersionName = "Version"; // Do not rename (binary serialization)
@@ -108,7 +113,7 @@ namespace System.Collections.Generic
Entry[] entries = d._entries;
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
Add(entries[i].key, entries[i].value);
}
@@ -146,83 +151,38 @@ namespace System.Collections.Generic
HashHelpers.SerializationInfoTable.Add(this, info);
}
- public IEqualityComparer<TKey> Comparer
- {
- get
- {
- return (_comparer == null
+ public IEqualityComparer<TKey> Comparer =>
+ (_comparer == null
#if !MONO
- || _comparer is NonRandomizedStringEqualityComparer
+ || _comparer is NonRandomizedStringEqualityComparer
#endif
- ) ? EqualityComparer<TKey>.Default : _comparer;
- }
- }
+ ) ?
+ EqualityComparer<TKey>.Default :
+ _comparer;
- public int Count
- {
- get { return _count - _freeCount; }
- }
+ public int Count => _count - _freeCount;
- public KeyCollection Keys
- {
- get
- {
- if (_keys == null) _keys = new KeyCollection(this);
- return _keys;
- }
- }
+ public KeyCollection Keys => _keys ??= new KeyCollection(this);
- ICollection<TKey> IDictionary<TKey, TValue>.Keys
- {
- get
- {
- if (_keys == null) _keys = new KeyCollection(this);
- return _keys;
- }
- }
+ ICollection<TKey> IDictionary<TKey, TValue>.Keys => _keys ??= new KeyCollection(this);
- IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
- {
- get
- {
- if (_keys == null) _keys = new KeyCollection(this);
- return _keys;
- }
- }
+ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _keys ??= new KeyCollection(this);
- public ValueCollection Values
- {
- get
- {
- if (_values == null) _values = new ValueCollection(this);
- return _values;
- }
- }
+ public ValueCollection Values => _values ??= new ValueCollection(this);
- ICollection<TValue> IDictionary<TKey, TValue>.Values
- {
- get
- {
- if (_values == null) _values = new ValueCollection(this);
- return _values;
- }
- }
+ ICollection<TValue> IDictionary<TKey, TValue>.Values => _values ??= new ValueCollection(this);
- IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
- {
- get
- {
- if (_values == null) _values = new ValueCollection(this);
- return _values;
- }
- }
+ IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _values ??= new ValueCollection(this);
public TValue this[TKey key]
{
get
{
- int i = FindEntry(key);
- if (i >= 0) return _entries[i].value;
+ ref TValue value = ref FindValue(key);
+ if (!Unsafe.IsNullRef(ref value))
+ {
+ return value;
+ }
ThrowHelper.ThrowKeyNotFoundException(key);
return default;
}
@@ -248,8 +208,8 @@ namespace System.Collections.Generic
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
{
- int i = FindEntry(keyValuePair.Key);
- if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
+ ref TValue value = ref FindValue(keyValuePair.Key);
+ if (!Unsafe.IsNullRef(ref value) && EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value))
{
return true;
}
@@ -258,8 +218,8 @@ namespace System.Collections.Generic
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
{
- int i = FindEntry(keyValuePair.Key);
- if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
+ ref TValue value = ref FindValue(keyValuePair.Key);
+ if (!Unsafe.IsNullRef(ref value) && EqualityComparer<TValue>.Default.Equals(value, keyValuePair.Value))
{
Remove(keyValuePair.Key);
return true;
@@ -272,6 +232,9 @@ namespace System.Collections.Generic
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, 0, _buckets.Length);
_count = 0;
@@ -279,11 +242,10 @@ namespace System.Collections.Generic
_freeCount = 0;
Array.Clear(_entries, 0, count);
}
- _version++;
}
public bool ContainsKey(TKey key)
- => FindEntry(key) >= 0;
+ => !Unsafe.IsNullRef(ref FindValue(key));
public bool ContainsValue(TValue value)
{
@@ -292,17 +254,17 @@ namespace System.Collections.Generic
{
for (int i = 0; i < _count; i++)
{
- if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
+ if (entries[i].next >= -1 && entries[i].value == null) return true;
}
}
else
{
- if (default(TValue) != null)
+ if (default(TValue) != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
for (int i = 0; i < _count; i++)
{
- if (entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, value)) return true;
+ if (entries[i].next >= -1 && EqualityComparer<TValue>.Default.Equals(entries[i].value, value)) return true;
}
}
else
@@ -313,7 +275,7 @@ namespace System.Collections.Generic
EqualityComparer<TValue> defaultComparer = EqualityComparer<TValue>.Default;
for (int i = 0; i < _count; i++)
{
- if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true;
+ if (entries[i].next >= -1 && defaultComparer.Equals(entries[i].value, value)) return true;
}
}
}
@@ -341,7 +303,7 @@ namespace System.Collections.Generic
Entry[] entries = _entries;
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
array[index++] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
}
@@ -373,46 +335,53 @@ namespace System.Collections.Generic
}
}
- private int FindEntry(TKey key)
+ private ref TValue FindValue(TKey key)
{
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- int i = -1;
int[] buckets = _buckets;
- Entry[] entries = _entries;
- int collisionCount = 0;
+ ref Entry entry = ref Unsafe.NullRef<Entry>();
if (buckets != null)
{
+ Debug.Assert(_entries != null, "expected entries to be != null");
IEqualityComparer<TKey> comparer = _comparer;
if (comparer == null)
{
- int hashCode = key.GetHashCode() & 0x7FFFFFFF;
- // Value in _buckets is 1-based
- i = buckets[hashCode % buckets.Length] - 1;
- if (default(TKey) != null)
+ uint hashCode = (uint)key.GetHashCode();
+ int i = buckets[hashCode % (uint)buckets.Length];
+ Entry[] entries = _entries;
+ uint collisionCount = 0;
+ if (default(TKey) != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
+
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.key, key))
{
- // 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();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- } while (true);
+ } 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
{
@@ -420,54 +389,77 @@ namespace System.Collections.Generic
// https://github.com/dotnet/coreclr/issues/17273
// So cache in a local rather than get EqualityComparer per loop iteration
EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
+
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && defaultComparer.Equals(entry.key, key))
{
- // 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();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- } while (true);
+ } 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
{
- int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
- // Value in _buckets is 1-based
- i = buckets[hashCode % buckets.Length] - 1;
+ uint hashCode = (uint)comparer.GetHashCode(key);
+ int i = buckets[hashCode % (uint)buckets.Length];
+ Entry[] entries = _entries;
+ uint collisionCount = 0;
+ // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+ i--;
do
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test in if to drop range check for following array access
- if ((uint)i >= (uint)entries.Length ||
- (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)))
+ if ((uint)i >= (uint)entries.Length)
{
- break;
+ goto ReturnNotFound;
}
- i = entries[i].next;
- if (collisionCount >= entries.Length)
+ entry = ref entries[i];
+ if (entry.hashCode == hashCode && comparer.Equals(entry.key, key))
{
- // 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();
+ goto ReturnFound;
}
+
+ i = entry.next;
+
collisionCount++;
- } while (true);
+ } 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;
}
}
- return i;
+ goto ReturnNotFound;
+
+ ConcurrentOperation:
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ ReturnFound:
+ ref TValue value = ref entry.value;
+ Return:
+ return ref value;
+ ReturnNotFound:
+ value = ref Unsafe.NullRef<TValue>();
+ goto Return;
}
private int Initialize(int capacity)
@@ -488,28 +480,29 @@ namespace System.Collections.Generic
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- _version++;
if (_buckets == null)
{
Initialize(0);
}
+ Debug.Assert(_buckets != null);
Entry[] entries = _entries;
- IEqualityComparer<TKey> comparer = _comparer;
+ Debug.Assert(entries != null, "expected entries to be non-null");
- int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF;
+ IEqualityComparer<TKey> comparer = _comparer;
+ uint hashCode = (uint)((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key));
- int collisionCount = 0;
- ref int bucket = ref _buckets[hashCode % _buckets.Length];
+ uint collisionCount = 0;
+ ref int bucket = ref _buckets[hashCode % (uint)_buckets.Length];
// Value in _buckets is 1-based
int i = bucket - 1;
if (comparer == null)
{
- if (default(TKey) != null)
+ if (default(TKey) != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
- do
+ while (true)
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test uint in if rather than loop condition to drop range check for following array access
@@ -523,6 +516,7 @@ namespace System.Collections.Generic
if (behavior == InsertionBehavior.OverwriteExisting)
{
entries[i].value = value;
+ _version++;
return true;
}
@@ -535,14 +529,15 @@ namespace System.Collections.Generic
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ 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();
}
- collisionCount++;
- } while (true);
+ }
}
else
{
@@ -550,7 +545,7 @@ namespace System.Collections.Generic
// https://github.com/dotnet/coreclr/issues/17273
// So cache in a local rather than get EqualityComparer per loop iteration
EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
- do
+ while (true)
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test uint in if rather than loop condition to drop range check for following array access
@@ -564,6 +559,7 @@ namespace System.Collections.Generic
if (behavior == InsertionBehavior.OverwriteExisting)
{
entries[i].value = value;
+ _version++;
return true;
}
@@ -576,19 +572,20 @@ namespace System.Collections.Generic
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ 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();
}
- collisionCount++;
- } while (true);
+ }
}
}
else
{
- do
+ while (true)
{
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
// Test uint in if rather than loop condition to drop range check for following array access
@@ -602,6 +599,7 @@ namespace System.Collections.Generic
if (behavior == InsertionBehavior.OverwriteExisting)
{
entries[i].value = value;
+ _version++;
return true;
}
@@ -614,20 +612,17 @@ namespace System.Collections.Generic
}
i = entries[i].next;
- if (collisionCount >= entries.Length)
+
+ 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();
}
- collisionCount++;
- } while (true);
-
+ }
}
- // Can be improved with "Ref Local Reassignment"
- // https://github.com/dotnet/csharplang/blob/master/proposals/ref-local-reassignment.md
- bool resized = false;
bool updateFreeList = false;
int index;
if (_freeCount > 0)
@@ -642,31 +637,33 @@ namespace System.Collections.Generic
if (count == entries.Length)
{
Resize();
- resized = true;
+ bucket = ref _buckets[hashCode % (uint)_buckets.Length];
}
index = count;
_count = count + 1;
entries = _entries;
}
- ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket;
ref Entry entry = ref entries[index];
if (updateFreeList)
{
- _freeList = entry.next;
+ Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow");
+
+ _freeList = StartOfFreeList - entries[_freeList].next;
}
entry.hashCode = hashCode;
// Value in _buckets is 1-based
- entry.next = targetBucket - 1;
+ entry.next = bucket - 1;
entry.key = key;
entry.value = value;
// Value in _buckets is 1-based
- targetBucket = index + 1;
+ bucket = index + 1;
+ _version++;
-#if !MONO
+#if !MONO
// Value types never rehash
- if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer)
+ if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
// If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
// i.e. EqualityComparer<string>.Default.
@@ -683,14 +680,14 @@ namespace System.Collections.Generic
if (siInfo == null)
{
- // We can return immediately if this function is called twice.
+ // We can return immediately if this function is called twice.
// Note we remove the serialization info from the table at the end of this method.
return;
}
int realVersion = siInfo.GetInt32(VersionName);
int hashsize = siInfo.GetInt32(HashSizeName);
- _comparer = (IEqualityComparer<TKey>)siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>));
+ _comparer = (IEqualityComparer<TKey>)siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>)); // When serialized if comparer is null, we use the default.
if (hashsize != 0)
{
@@ -728,6 +725,9 @@ namespace System.Collections.Generic
private void Resize(int newSize, bool forceNewHashCodes)
{
#if !MONO
+ // Value types never rehash
+ Debug.Assert(!forceNewHashCodes || default(TKey) == null); // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
+ Debug.Assert(_entries != null, "_entries should be non-null");
Debug.Assert(newSize >= _entries.Length);
#endif
@@ -737,25 +737,23 @@ namespace System.Collections.Generic
int count = _count;
Array.Copy(_entries, 0, entries, 0, count);
- if (default(TKey) == null && forceNewHashCodes)
+ if (default(TKey) == null && forceNewHashCodes) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
{
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
-#if !MONO
Debug.Assert(_comparer == null);
-#endif
- entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF);
+ entries[i].hashCode = (uint)entries[i].key.GetHashCode();
}
}
}
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
- int bucket = entries[i].hashCode % newSize;
+ uint bucket = entries[i].hashCode % (uint)newSize;
// Value in _buckets is 1-based
entries[i].next = buckets[bucket] - 1;
// Value in _buckets is 1-based
@@ -777,30 +775,36 @@ namespace System.Collections.Generic
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- if (_buckets != null)
+ int[] buckets = _buckets;
+ Entry[] entries = _entries;
+ if (buckets != null)
{
- int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
- int bucket = hashCode % _buckets.Length;
+ Debug.Assert(entries != null, "entries should be non-null");
+ uint collisionCount = 0;
+ uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode());
+ uint bucket = hashCode % (uint)buckets.Length;
int last = -1;
- // Value in _buckets is 1-based
- int i = _buckets[bucket] - 1;
+ // Value in buckets is 1-based
+ int i = buckets[bucket] - 1;
while (i >= 0)
{
- ref Entry entry = ref _entries[i];
+ ref Entry entry = ref entries[i];
if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
{
if (last < 0)
{
- // Value in _buckets is 1-based
- _buckets[bucket] = entry.next + 1;
+ // Value in buckets is 1-based
+ buckets[bucket] = entry.next + 1;
}
else
{
- _entries[last].next = entry.next;
+ entries[last].next = entry.next;
}
- entry.hashCode = -1;
- entry.next = _freeList;
+
+ 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 (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
{
@@ -812,12 +816,19 @@ namespace System.Collections.Generic
}
_freeList = i;
_freeCount++;
- _version++;
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;
@@ -826,40 +837,45 @@ namespace System.Collections.Generic
// This overload is a copy of the overload Remove(TKey key) with one additional
// statement to copy the value for entry being removed into the output parameter.
// Code has been intentionally duplicated for performance reasons.
- public bool Remove(TKey key, out TValue value)
+ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
{
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- if (_buckets != null)
+ int[] buckets = _buckets;
+ Entry[] entries = _entries;
+ if (buckets != null)
{
- int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
- int bucket = hashCode % _buckets.Length;
+ Debug.Assert(entries != null, "entries should be non-null");
+ uint collisionCount = 0;
+ uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode());
+ uint bucket = hashCode % (uint)buckets.Length;
int last = -1;
- // Value in _buckets is 1-based
- int i = _buckets[bucket] - 1;
+ // Value in buckets is 1-based
+ int i = buckets[bucket] - 1;
while (i >= 0)
{
- ref Entry entry = ref _entries[i];
+ ref Entry entry = ref entries[i];
if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
{
if (last < 0)
{
- // Value in _buckets is 1-based
- _buckets[bucket] = entry.next + 1;
+ // Value in buckets is 1-based
+ buckets[bucket] = entry.next + 1;
}
else
{
- _entries[last].next = entry.next;
+ entries[last].next = entry.next;
}
value = entry.value;
- entry.hashCode = -1;
- entry.next = _freeList;
+ 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 (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
{
@@ -871,24 +887,31 @@ namespace System.Collections.Generic
}
_freeList = i;
_freeCount++;
- _version++;
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();
+ }
}
}
value = default;
return false;
}
- public bool TryGetValue(TKey key, out TValue value)
+ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
- int i = FindEntry(key);
- if (i >= 0)
+ ref TValue valRef = ref FindValue(key);
+ if (!Unsafe.IsNullRef(ref valRef))
{
- value = _entries[i].value;
+ value = valRef;
return true;
}
value = default;
@@ -925,7 +948,7 @@ namespace System.Collections.Generic
Entry[] entries = _entries;
for (int i = 0; i < _count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value);
}
@@ -945,7 +968,7 @@ namespace System.Collections.Generic
Entry[] entries = _entries;
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0)
+ if (entries[i].next >= -1)
{
objects[index++] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
}
@@ -971,6 +994,7 @@ namespace System.Collections.Generic
int currentCapacity = _entries == null ? 0 : _entries.Length;
if (currentCapacity >= capacity)
return currentCapacity;
+ _version++;
if (_buckets == null)
return Initialize(capacity);
int newSize = HashHelpers.GetPrime(capacity);
@@ -980,12 +1004,12 @@ namespace System.Collections.Generic
/// <summary>
/// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
- ///
- /// This method can be used to minimize the memory overhead
- /// once it is known that no new elements will be added.
- ///
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
+ ///
/// To allocate minimum size storage array, execute the following statements:
- ///
+ ///
/// dictionary.Clear();
/// dictionary.TrimExcess();
/// </summary>
@@ -994,9 +1018,9 @@ namespace System.Collections.Generic
/// <summary>
/// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
- ///
- /// This method can be used to minimize the memory overhead
- /// once it is known that no new elements will be added.
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
/// </summary>
public void TrimExcess(int capacity)
{
@@ -1010,20 +1034,21 @@ namespace System.Collections.Generic
return;
int oldCount = _count;
+ _version++;
Initialize(newSize);
Entry[] entries = _entries;
int[] buckets = _buckets;
int count = 0;
for (int i = 0; i < oldCount; i++)
{
- int hashCode = oldEntries[i].hashCode;
- if (hashCode >= 0)
+ uint hashCode = oldEntries[i].hashCode; // At this point, we know we have entries.
+ if (oldEntries[i].next >= -1)
{
ref Entry entry = ref entries[count];
entry = oldEntries[i];
- int bucket = hashCode % newSize;
+ uint bucket = hashCode % (uint)newSize;
// Value in _buckets is 1-based
- entry.next = buckets[bucket] - 1;
+ entry.next = buckets[bucket] - 1; // If we get here, we have entries, therefore buckets is not null.
// Value in _buckets is 1-based
buckets[bucket] = count + 1;
count++;
@@ -1035,17 +1060,7 @@ namespace System.Collections.Generic
bool ICollection.IsSynchronized => false;
- object ICollection.SyncRoot
- {
- get
- {
- if (_syncRoot == null)
- {
- Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
- }
- return _syncRoot;
- }
- }
+ object ICollection.SyncRoot => this;
bool IDictionary.IsFixedSize => false;
@@ -1061,10 +1076,10 @@ namespace System.Collections.Generic
{
if (IsCompatibleKey(key))
{
- int i = FindEntry((TKey)key);
- if (i >= 0)
+ ref TValue value = ref FindValue((TKey)key);
+ if (!Unsafe.IsNullRef(ref value))
{
- return _entries[i].value;
+ return value;
}
}
return null;
@@ -1102,7 +1117,7 @@ namespace System.Collections.Generic
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
- return (key is TKey);
+ return key is TKey;
}
void IDictionary.Add(object key, object value)
@@ -1159,11 +1174,11 @@ namespace System.Collections.Generic
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>,
IDictionaryEnumerator
{
- private Dictionary<TKey, TValue> _dictionary;
- private int _version;
+ private readonly Dictionary<TKey, TValue> _dictionary;
+ private readonly int _version;
private int _index;
private KeyValuePair<TKey, TValue> _current;
- private int _getEnumeratorRetType; // What should Enumerator.Current return?
+ private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
internal const int DictEntry = 1;
internal const int KeyValuePair = 2;
@@ -1185,12 +1200,12 @@ namespace System.Collections.Generic
}
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
- // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
+ // 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++];
- if (entry.hashCode >= 0)
+ if (entry.next >= -1)
{
_current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
return true;
@@ -1286,7 +1301,7 @@ namespace System.Collections.Generic
#endif
public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey>
{
- private Dictionary<TKey, TValue> _dictionary;
+ private readonly Dictionary<TKey, TValue> _dictionary;
public KeyCollection(Dictionary<TKey, TValue> dictionary)
{
@@ -1321,7 +1336,7 @@ namespace System.Collections.Generic
Entry[] entries = _dictionary._entries;
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0) array[index++] = entries[i].key;
+ if (entries[i].next >= -1) array[index++] = entries[i].key;
}
}
@@ -1381,7 +1396,7 @@ namespace System.Collections.Generic
{
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0) objects[index++] = entries[i].key;
+ if (entries[i].next >= -1) objects[index++] = entries[i].key;
}
}
catch (ArrayTypeMismatchException)
@@ -1400,10 +1415,10 @@ namespace System.Collections.Generic
#endif
public struct Enumerator : IEnumerator<TKey>, IEnumerator
{
- private Dictionary<TKey, TValue> _dictionary;
+ private readonly Dictionary<TKey, TValue> _dictionary;
private int _index;
- private int _version;
- private TKey _currentKey;
+ private readonly int _version;
+ [AllowNull, MaybeNull] private TKey _currentKey;
internal Enumerator(Dictionary<TKey, TValue> dictionary)
{
@@ -1428,7 +1443,7 @@ namespace System.Collections.Generic
{
ref Entry entry = ref _dictionary._entries[_index++];
- if (entry.hashCode >= 0)
+ if (entry.next >= -1)
{
_currentKey = entry.key;
return true;
@@ -1475,7 +1490,7 @@ namespace System.Collections.Generic
#endif
public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue>
{
- private Dictionary<TKey, TValue> _dictionary;
+ private readonly Dictionary<TKey, TValue> _dictionary;
public ValueCollection(Dictionary<TKey, TValue> dictionary)
{
@@ -1496,7 +1511,7 @@ namespace System.Collections.Generic
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
- if (index < 0 || index > array.Length)
+ if ((uint)index > array.Length)
{
ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
}
@@ -1510,7 +1525,7 @@ namespace System.Collections.Generic
Entry[] entries = _dictionary._entries;
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0) array[index++] = entries[i].value;
+ if (entries[i].next >= -1) array[index++] = entries[i].value;
}
}
@@ -1570,7 +1585,7 @@ namespace System.Collections.Generic
{
for (int i = 0; i < count; i++)
{
- if (entries[i].hashCode >= 0) objects[index++] = entries[i].value;
+ if (entries[i].next >= -1) objects[index++] = entries[i].value;
}
}
catch (ArrayTypeMismatchException)
@@ -1589,10 +1604,10 @@ namespace System.Collections.Generic
#endif
public struct Enumerator : IEnumerator<TValue>, IEnumerator
{
- private Dictionary<TKey, TValue> _dictionary;
+ private readonly Dictionary<TKey, TValue> _dictionary;
private int _index;
- private int _version;
- private TValue _currentValue;
+ private readonly int _version;
+ [AllowNull, MaybeNull] private TValue _currentValue;
internal Enumerator(Dictionary<TKey, TValue> dictionary)
{
@@ -1617,7 +1632,7 @@ namespace System.Collections.Generic
{
ref Entry entry = ref _dictionary._entries[_index++];
- if (entry.hashCode >= 0)
+ if (entry.next >= -1)
{
_currentValue = entry.value;
return true;