1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Diagnostics
;
6 using System
.Diagnostics
.CodeAnalysis
;
7 using System
.Runtime
.CompilerServices
;
8 using System
.Runtime
.Serialization
;
10 namespace System
.Collections
.Generic
13 /// Used internally to control behavior of insertion into a <see cref="Dictionary{TKey, TValue}"/>.
15 internal enum InsertionBehavior
: byte
18 /// The default insertion behavior.
23 /// Specifies that an existing entry with the same key should be overwritten if encountered.
25 OverwriteExisting
= 1,
28 /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
33 [DebuggerTypeProxy(typeof(IDictionaryDebugView
<,>))]
34 [DebuggerDisplay("Count = {Count}")]
36 [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
37 public class Dictionary
<TKey
, TValue
> : IDictionary
<TKey
, TValue
>, IDictionary
, IReadOnlyDictionary
<TKey
, TValue
>, ISerializable
, IDeserializationCallback where TKey
: notnull
41 // 0-based index of next entry in chain: -1 means end of chain
42 // also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
43 // so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
46 public TKey key
; // Key of entry
47 public TValue
value; // Value of entry
50 private int[]? _buckets
;
51 private Entry
[]? _entries
;
53 private int _freeList
;
54 private int _freeCount
;
56 private IEqualityComparer
<TKey
>? _comparer
;
57 private KeyCollection
? _keys
;
58 private ValueCollection
? _values
;
59 private const int StartOfFreeList
= -3;
61 // constants for serialization
62 private const string VersionName
= "Version"; // Do not rename (binary serialization)
63 private const string HashSizeName
= "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
64 private const string KeyValuePairsName
= "KeyValuePairs"; // Do not rename (binary serialization)
65 private const string ComparerName
= "Comparer"; // Do not rename (binary serialization)
67 public Dictionary() : this(0, null) { }
69 public Dictionary(int capacity
) : this(capacity
, null) { }
71 public Dictionary(IEqualityComparer
<TKey
>? comparer
) : this(0, comparer
) { }
73 public Dictionary(int capacity
, IEqualityComparer
<TKey
>? comparer
)
75 if (capacity
< 0) ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.capacity
);
76 if (capacity
> 0) Initialize(capacity
);
77 if (comparer
!= EqualityComparer
<TKey
>.Default
)
82 if (typeof(TKey
) == typeof(string) && _comparer
== null)
84 // To start, move off default comparer for string which is randomised
85 _comparer
= (IEqualityComparer
<TKey
>)NonRandomizedStringEqualityComparer
.Default
;
89 public Dictionary(IDictionary
<TKey
, TValue
> dictionary
) : this(dictionary
, null) { }
91 public Dictionary(IDictionary
<TKey
, TValue
> dictionary
, IEqualityComparer
<TKey
>? comparer
) :
92 this(dictionary
!= null ? dictionary
.Count
: 0, comparer
)
94 if (dictionary
== null)
96 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.dictionary
);
99 // It is likely that the passed-in dictionary is Dictionary<TKey,TValue>. When this is the case,
100 // avoid the enumerator allocation and overhead by looping through the entries array directly.
101 // We only do this when dictionary is Dictionary<TKey,TValue> and not a subclass, to maintain
102 // back-compat with subclasses that may have overridden the enumerator behavior.
103 if (dictionary
.GetType() == typeof(Dictionary
<TKey
, TValue
>))
105 Dictionary
<TKey
, TValue
> d
= (Dictionary
<TKey
, TValue
>)dictionary
;
106 int count
= d
._count
;
107 Entry
[]? entries
= d
._entries
;
108 for (int i
= 0; i
< count
; i
++)
110 if (entries
![i
].next
>= -1)
112 Add(entries
[i
].key
, entries
[i
].value);
118 foreach (KeyValuePair
<TKey
, TValue
> pair
in dictionary
)
120 Add(pair
.Key
, pair
.Value
);
124 public Dictionary(IEnumerable
<KeyValuePair
<TKey
, TValue
>> collection
) : this(collection
, null) { }
126 public Dictionary(IEnumerable
<KeyValuePair
<TKey
, TValue
>> collection
, IEqualityComparer
<TKey
>? comparer
) :
127 this((collection
as ICollection
<KeyValuePair
<TKey
, TValue
>>)?.Count
?? 0, comparer
)
129 if (collection
== null)
131 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.collection
);
134 foreach (KeyValuePair
<TKey
, TValue
> pair
in collection
)
136 Add(pair
.Key
, pair
.Value
);
140 protected Dictionary(SerializationInfo info
, StreamingContext context
)
142 // We can't do anything with the keys and values until the entire graph has been deserialized
143 // and we have a resonable estimate that GetHashCode is not going to fail. For the time being,
144 // we'll just cache this. The graph is not valid until OnDeserialization has been called.
145 HashHelpers
.SerializationInfoTable
.Add(this, info
);
148 public IEqualityComparer
<TKey
> Comparer
152 return (_comparer
== null || _comparer
is NonRandomizedStringEqualityComparer
) ? EqualityComparer
<TKey
>.Default
: _comparer
;
158 get { return _count - _freeCount; }
161 public KeyCollection Keys
165 if (_keys
== null) _keys
= new KeyCollection(this);
170 ICollection
<TKey
> IDictionary
<TKey
, TValue
>.Keys
174 if (_keys
== null) _keys
= new KeyCollection(this);
179 IEnumerable
<TKey
> IReadOnlyDictionary
<TKey
, TValue
>.Keys
183 if (_keys
== null) _keys
= new KeyCollection(this);
188 public ValueCollection Values
192 if (_values
== null) _values
= new ValueCollection(this);
197 ICollection
<TValue
> IDictionary
<TKey
, TValue
>.Values
201 if (_values
== null) _values
= new ValueCollection(this);
206 IEnumerable
<TValue
> IReadOnlyDictionary
<TKey
, TValue
>.Values
210 if (_values
== null) _values
= new ValueCollection(this);
215 public TValue
this[TKey key
]
219 int i
= FindEntry(key
);
220 if (i
>= 0) return _entries
![i
].value;
221 ThrowHelper
.ThrowKeyNotFoundException(key
);
226 bool modified
= TryInsert(key
, value, InsertionBehavior
.OverwriteExisting
);
227 Debug
.Assert(modified
);
231 public void Add(TKey key
, TValue
value)
233 bool modified
= TryInsert(key
, value, InsertionBehavior
.ThrowOnExisting
);
234 Debug
.Assert(modified
); // If there was an existing key and the Add failed, an exception will already have been thrown.
237 void ICollection
<KeyValuePair
<TKey
, TValue
>>.Add(KeyValuePair
<TKey
, TValue
> keyValuePair
)
238 => Add(keyValuePair
.Key
, keyValuePair
.Value
);
240 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.Contains(KeyValuePair
<TKey
, TValue
> keyValuePair
)
242 int i
= FindEntry(keyValuePair
.Key
);
243 if (i
>= 0 && EqualityComparer
<TValue
>.Default
.Equals(_entries
![i
].value, keyValuePair
.Value
))
250 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.Remove(KeyValuePair
<TKey
, TValue
> keyValuePair
)
252 int i
= FindEntry(keyValuePair
.Key
);
253 if (i
>= 0 && EqualityComparer
<TValue
>.Default
.Equals(_entries
![i
].value, keyValuePair
.Value
))
255 Remove(keyValuePair
.Key
);
266 Debug
.Assert(_buckets
!= null, "_buckets should be non-null");
267 Debug
.Assert(_entries
!= null, "_entries should be non-null");
269 Array
.Clear(_buckets
, 0, _buckets
.Length
);
274 Array
.Clear(_entries
, 0, count
);
278 public bool ContainsKey(TKey key
)
279 => FindEntry(key
) >= 0;
281 public bool ContainsValue(TValue
value)
283 Entry
[]? entries
= _entries
;
286 for (int i
= 0; i
< _count
; i
++)
288 if (entries
![i
].next
>= -1 && entries
[i
].value == null) return true;
293 if (default(TValue
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
295 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
296 for (int i
= 0; i
< _count
; i
++)
298 if (entries
![i
].next
>= -1 && EqualityComparer
<TValue
>.Default
.Equals(entries
[i
].value, value)) return true;
303 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
304 // https://github.com/dotnet/coreclr/issues/17273
305 // So cache in a local rather than get EqualityComparer per loop iteration
306 EqualityComparer
<TValue
> defaultComparer
= EqualityComparer
<TValue
>.Default
;
307 for (int i
= 0; i
< _count
; i
++)
309 if (entries
![i
].next
>= -1 && defaultComparer
.Equals(entries
[i
].value, value)) return true;
316 private void CopyTo(KeyValuePair
<TKey
, TValue
>[] array
, int index
)
320 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
323 if ((uint)index
> (uint)array
.Length
)
325 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
328 if (array
.Length
- index
< Count
)
330 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
334 Entry
[]? entries
= _entries
;
335 for (int i
= 0; i
< count
; i
++)
337 if (entries
![i
].next
>= -1)
339 array
[index
++] = new KeyValuePair
<TKey
, TValue
>(entries
[i
].key
, entries
[i
].value);
344 public Enumerator
GetEnumerator()
345 => new Enumerator(this, Enumerator
.KeyValuePair
);
347 IEnumerator
<KeyValuePair
<TKey
, TValue
>> IEnumerable
<KeyValuePair
<TKey
, TValue
>>.GetEnumerator()
348 => new Enumerator(this, Enumerator
.KeyValuePair
);
350 public virtual void GetObjectData(SerializationInfo info
, StreamingContext context
)
354 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.info
);
357 info
.AddValue(VersionName
, _version
);
358 info
.AddValue(ComparerName
, _comparer
?? EqualityComparer
<TKey
>.Default
, typeof(IEqualityComparer
<TKey
>));
359 info
.AddValue(HashSizeName
, _buckets
== null ? 0 : _buckets
.Length
); // This is the length of the bucket array
361 if (_buckets
!= null)
363 var array
= new KeyValuePair
<TKey
, TValue
>[Count
];
365 info
.AddValue(KeyValuePairsName
, array
, typeof(KeyValuePair
<TKey
, TValue
>[]));
369 private int FindEntry(TKey key
)
373 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
377 int[]? buckets
= _buckets
;
378 Entry
[]? entries
= _entries
;
379 int collisionCount
= 0;
382 Debug
.Assert(entries
!= null, "expected entries to be != null");
383 IEqualityComparer
<TKey
>? comparer
= _comparer
;
384 if (comparer
== null)
386 uint hashCode
= (uint)key
.GetHashCode();
387 // Value in _buckets is 1-based
388 i
= buckets
[hashCode
% (uint)buckets
.Length
] - 1;
389 if (default(TKey
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
391 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
394 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
395 // Test in if to drop range check for following array access
396 if ((uint)i
>= (uint)entries
.Length
|| (entries
[i
].hashCode
== hashCode
&& EqualityComparer
<TKey
>.Default
.Equals(entries
[i
].key
, key
)))
402 if (collisionCount
>= entries
.Length
)
404 // The chain of entries forms a loop; which means a concurrent update has happened.
405 // Break out of the loop and throw, rather than looping forever.
406 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
413 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
414 // https://github.com/dotnet/coreclr/issues/17273
415 // So cache in a local rather than get EqualityComparer per loop iteration
416 EqualityComparer
<TKey
> defaultComparer
= EqualityComparer
<TKey
>.Default
;
419 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
420 // Test in if to drop range check for following array access
421 if ((uint)i
>= (uint)entries
.Length
|| (entries
[i
].hashCode
== hashCode
&& defaultComparer
.Equals(entries
[i
].key
, key
)))
427 if (collisionCount
>= entries
.Length
)
429 // The chain of entries forms a loop; which means a concurrent update has happened.
430 // Break out of the loop and throw, rather than looping forever.
431 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
439 uint hashCode
= (uint)comparer
.GetHashCode(key
);
440 // Value in _buckets is 1-based
441 i
= buckets
[hashCode
% (uint)buckets
.Length
] - 1;
444 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
445 // Test in if to drop range check for following array access
446 if ((uint)i
>= (uint)entries
.Length
||
447 (entries
[i
].hashCode
== hashCode
&& comparer
.Equals(entries
[i
].key
, key
)))
453 if (collisionCount
>= entries
.Length
)
455 // The chain of entries forms a loop; which means a concurrent update has happened.
456 // Break out of the loop and throw, rather than looping forever.
457 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
467 private int Initialize(int capacity
)
469 int size
= HashHelpers
.GetPrime(capacity
);
472 _buckets
= new int[size
];
473 _entries
= new Entry
[size
];
478 private bool TryInsert(TKey key
, TValue
value, InsertionBehavior behavior
)
482 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
485 if (_buckets
== null)
489 Debug
.Assert(_buckets
!= null);
491 Entry
[]? entries
= _entries
;
492 Debug
.Assert(entries
!= null, "expected entries to be non-null");
494 IEqualityComparer
<TKey
>? comparer
= _comparer
;
495 uint hashCode
= (uint)((comparer
== null) ? key
.GetHashCode() : comparer
.GetHashCode(key
));
497 int collisionCount
= 0;
498 ref int bucket
= ref _buckets
[hashCode
% (uint)_buckets
.Length
];
499 // Value in _buckets is 1-based
502 if (comparer
== null)
504 if (default(TKey
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
506 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
509 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
510 // Test uint in if rather than loop condition to drop range check for following array access
511 if ((uint)i
>= (uint)entries
.Length
)
516 if (entries
[i
].hashCode
== hashCode
&& EqualityComparer
<TKey
>.Default
.Equals(entries
[i
].key
, key
))
518 if (behavior
== InsertionBehavior
.OverwriteExisting
)
520 entries
[i
].value = value;
525 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
527 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
534 if (collisionCount
>= entries
.Length
)
536 // The chain of entries forms a loop; which means a concurrent update has happened.
537 // Break out of the loop and throw, rather than looping forever.
538 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
545 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
546 // https://github.com/dotnet/coreclr/issues/17273
547 // So cache in a local rather than get EqualityComparer per loop iteration
548 EqualityComparer
<TKey
> defaultComparer
= EqualityComparer
<TKey
>.Default
;
551 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
552 // Test uint in if rather than loop condition to drop range check for following array access
553 if ((uint)i
>= (uint)entries
.Length
)
558 if (entries
[i
].hashCode
== hashCode
&& defaultComparer
.Equals(entries
[i
].key
, key
))
560 if (behavior
== InsertionBehavior
.OverwriteExisting
)
562 entries
[i
].value = value;
567 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
569 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
576 if (collisionCount
>= entries
.Length
)
578 // The chain of entries forms a loop; which means a concurrent update has happened.
579 // Break out of the loop and throw, rather than looping forever.
580 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
590 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
591 // Test uint in if rather than loop condition to drop range check for following array access
592 if ((uint)i
>= (uint)entries
.Length
)
597 if (entries
[i
].hashCode
== hashCode
&& comparer
.Equals(entries
[i
].key
, key
))
599 if (behavior
== InsertionBehavior
.OverwriteExisting
)
601 entries
[i
].value = value;
606 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
608 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
615 if (collisionCount
>= entries
.Length
)
617 // The chain of entries forms a loop; which means a concurrent update has happened.
618 // Break out of the loop and throw, rather than looping forever.
619 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
626 bool updateFreeList
= false;
631 updateFreeList
= true;
637 if (count
== entries
.Length
)
640 bucket
= ref _buckets
[hashCode
% (uint)_buckets
.Length
];
647 ref Entry entry
= ref entries
![index
];
651 Debug
.Assert((StartOfFreeList
- entries
[_freeList
].next
) >= -1, "shouldn't overflow because `next` cannot underflow");
653 _freeList
= StartOfFreeList
- entries
[_freeList
].next
;
655 entry
.hashCode
= hashCode
;
656 // Value in _buckets is 1-based
657 entry
.next
= bucket
- 1;
660 // Value in _buckets is 1-based
664 // Value types never rehash
665 if (default(TKey
)! == null && collisionCount
> HashHelpers
.HashCollisionThreshold
&& comparer
is NonRandomizedStringEqualityComparer
) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
667 // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
668 // i.e. EqualityComparer<string>.Default.
670 Resize(entries
.Length
, true);
676 public virtual void OnDeserialization(object? sender
)
678 HashHelpers
.SerializationInfoTable
.TryGetValue(this, out SerializationInfo
? siInfo
);
682 // We can return immediately if this function is called twice.
683 // Note we remove the serialization info from the table at the end of this method.
687 int realVersion
= siInfo
.GetInt32(VersionName
);
688 int hashsize
= siInfo
.GetInt32(HashSizeName
);
689 _comparer
= (IEqualityComparer
<TKey
>)siInfo
.GetValue(ComparerName
, typeof(IEqualityComparer
<TKey
>))!; // When serialized if comparer is null, we use the default.
693 Initialize(hashsize
);
695 KeyValuePair
<TKey
, TValue
>[]? array
= (KeyValuePair
<TKey
, TValue
>[]?)
696 siInfo
.GetValue(KeyValuePairsName
, typeof(KeyValuePair
<TKey
, TValue
>[]));
700 ThrowHelper
.ThrowSerializationException(ExceptionResource
.Serialization_MissingKeys
);
703 for (int i
= 0; i
< array
.Length
; i
++)
705 if (array
[i
].Key
== null)
707 ThrowHelper
.ThrowSerializationException(ExceptionResource
.Serialization_NullKey
);
709 Add(array
[i
].Key
, array
[i
].Value
);
717 _version
= realVersion
;
718 HashHelpers
.SerializationInfoTable
.Remove(this);
721 private void Resize()
722 => Resize(HashHelpers
.ExpandPrime(_count
), false);
724 private void Resize(int newSize
, bool forceNewHashCodes
)
726 // Value types never rehash
727 Debug
.Assert(!forceNewHashCodes
|| default(TKey
)! == null); // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
728 Debug
.Assert(_entries
!= null, "_entries should be non-null");
729 Debug
.Assert(newSize
>= _entries
.Length
);
731 int[] buckets
= new int[newSize
];
732 Entry
[] entries
= new Entry
[newSize
];
735 Array
.Copy(_entries
, 0, entries
, 0, count
);
737 if (default(TKey
)! == null && forceNewHashCodes
) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
739 for (int i
= 0; i
< count
; i
++)
741 if (entries
[i
].next
>= -1)
743 Debug
.Assert(_comparer
== null);
744 entries
[i
].hashCode
= (uint)entries
[i
].key
.GetHashCode();
749 for (int i
= 0; i
< count
; i
++)
751 if (entries
[i
].next
>= -1)
753 uint bucket
= entries
[i
].hashCode
% (uint)newSize
;
754 // Value in _buckets is 1-based
755 entries
[i
].next
= buckets
[bucket
] - 1;
756 // Value in _buckets is 1-based
757 buckets
[bucket
] = i
+ 1;
765 // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
766 // statement to copy the value for entry being removed into the output parameter.
767 // Code has been intentionally duplicated for performance reasons.
768 public bool Remove(TKey key
)
772 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
775 int[]? buckets
= _buckets
;
776 Entry
[]? entries
= _entries
;
777 int collisionCount
= 0;
780 Debug
.Assert(entries
!= null, "entries should be non-null");
781 uint hashCode
= (uint)(_comparer
?.GetHashCode(key
) ?? key
.GetHashCode());
782 uint bucket
= hashCode
% (uint)buckets
.Length
;
784 // Value in buckets is 1-based
785 int i
= buckets
[bucket
] - 1;
788 ref Entry entry
= ref entries
[i
];
790 if (entry
.hashCode
== hashCode
&& (_comparer
?.Equals(entry
.key
, key
) ?? EqualityComparer
<TKey
>.Default
.Equals(entry
.key
, key
)))
794 // Value in buckets is 1-based
795 buckets
[bucket
] = entry
.next
+ 1;
799 entries
[last
].next
= entry
.next
;
802 Debug
.Assert((StartOfFreeList
- _freeList
) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
804 entry
.next
= StartOfFreeList
- _freeList
;
806 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TKey
>())
808 entry
.key
= default!;
810 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TValue
>())
812 entry
.value = default!;
821 if (collisionCount
>= entries
.Length
)
823 // The chain of entries forms a loop; which means a concurrent update has happened.
824 // Break out of the loop and throw, rather than looping forever.
825 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
833 // This overload is a copy of the overload Remove(TKey key) with one additional
834 // statement to copy the value for entry being removed into the output parameter.
835 // Code has been intentionally duplicated for performance reasons.
836 public bool Remove(TKey key
, [MaybeNullWhen(false)] out TValue
value)
840 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
843 int[]? buckets
= _buckets
;
844 Entry
[]? entries
= _entries
;
845 int collisionCount
= 0;
848 Debug
.Assert(entries
!= null, "entries should be non-null");
849 uint hashCode
= (uint)(_comparer
?.GetHashCode(key
) ?? key
.GetHashCode());
850 uint bucket
= hashCode
% (uint)buckets
.Length
;
852 // Value in buckets is 1-based
853 int i
= buckets
[bucket
] - 1;
856 ref Entry entry
= ref entries
[i
];
858 if (entry
.hashCode
== hashCode
&& (_comparer
?.Equals(entry
.key
, key
) ?? EqualityComparer
<TKey
>.Default
.Equals(entry
.key
, key
)))
862 // Value in buckets is 1-based
863 buckets
[bucket
] = entry
.next
+ 1;
867 entries
[last
].next
= entry
.next
;
872 Debug
.Assert((StartOfFreeList
- _freeList
) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
874 entry
.next
= StartOfFreeList
- _freeList
;
876 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TKey
>())
878 entry
.key
= default!;
880 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TValue
>())
882 entry
.value = default!;
891 if (collisionCount
>= entries
.Length
)
893 // The chain of entries forms a loop; which means a concurrent update has happened.
894 // Break out of the loop and throw, rather than looping forever.
895 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
904 public bool TryGetValue(TKey key
, [MaybeNullWhen(false)] out TValue
value)
906 int i
= FindEntry(key
);
909 value = _entries
![i
].value;
916 public bool TryAdd(TKey key
, TValue
value)
917 => TryInsert(key
, value, InsertionBehavior
.None
);
919 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.IsReadOnly
=> false;
921 void ICollection
<KeyValuePair
<TKey
, TValue
>>.CopyTo(KeyValuePair
<TKey
, TValue
>[] array
, int index
)
922 => CopyTo(array
, index
);
924 void ICollection
.CopyTo(Array array
, int index
)
927 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
929 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
930 if (array
.GetLowerBound(0) != 0)
931 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
932 if ((uint)index
> (uint)array
.Length
)
933 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
934 if (array
.Length
- index
< Count
)
935 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
937 if (array
is KeyValuePair
<TKey
, TValue
>[] pairs
)
939 CopyTo(pairs
, index
);
941 else if (array
is DictionaryEntry
[] dictEntryArray
)
943 Entry
[]? entries
= _entries
;
944 for (int i
= 0; i
< _count
; i
++)
946 if (entries
![i
].next
>= -1)
948 dictEntryArray
[index
++] = new DictionaryEntry(entries
[i
].key
, entries
[i
].value);
954 object[]? objects
= array
as object[];
957 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
963 Entry
[]? entries
= _entries
;
964 for (int i
= 0; i
< count
; i
++)
966 if (entries
![i
].next
>= -1)
968 objects
[index
++] = new KeyValuePair
<TKey
, TValue
>(entries
[i
].key
, entries
[i
].value);
972 catch (ArrayTypeMismatchException
)
974 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
979 IEnumerator IEnumerable
.GetEnumerator()
980 => new Enumerator(this, Enumerator
.KeyValuePair
);
983 /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
985 public int EnsureCapacity(int capacity
)
988 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.capacity
);
989 int currentCapacity
= _entries
== null ? 0 : _entries
.Length
;
990 if (currentCapacity
>= capacity
)
991 return currentCapacity
;
993 if (_buckets
== null)
994 return Initialize(capacity
);
995 int newSize
= HashHelpers
.GetPrime(capacity
);
996 Resize(newSize
, forceNewHashCodes
: false);
1001 /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
1003 /// This method can be used to minimize the memory overhead
1004 /// once it is known that no new elements will be added.
1006 /// To allocate minimum size storage array, execute the following statements:
1008 /// dictionary.Clear();
1009 /// dictionary.TrimExcess();
1011 public void TrimExcess()
1012 => TrimExcess(Count
);
1015 /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
1017 /// This method can be used to minimize the memory overhead
1018 /// once it is known that no new elements will be added.
1020 public void TrimExcess(int capacity
)
1022 if (capacity
< Count
)
1023 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.capacity
);
1024 int newSize
= HashHelpers
.GetPrime(capacity
);
1026 Entry
[]? oldEntries
= _entries
;
1027 int currentCapacity
= oldEntries
== null ? 0 : oldEntries
.Length
;
1028 if (newSize
>= currentCapacity
)
1031 int oldCount
= _count
;
1033 Initialize(newSize
);
1034 Entry
[]? entries
= _entries
;
1035 int[]? buckets
= _buckets
;
1037 for (int i
= 0; i
< oldCount
; i
++)
1039 uint hashCode
= oldEntries
![i
].hashCode
; // At this point, we know we have entries.
1040 if (oldEntries
[i
].next
>= -1)
1042 ref Entry entry
= ref entries
![count
];
1043 entry
= oldEntries
[i
];
1044 uint bucket
= hashCode
% (uint)newSize
;
1045 // Value in _buckets is 1-based
1046 entry
.next
= buckets
![bucket
] - 1; // If we get here, we have entries, therefore buckets is not null.
1047 // Value in _buckets is 1-based
1048 buckets
[bucket
] = count
+ 1;
1056 bool ICollection
.IsSynchronized
=> false;
1058 object ICollection
.SyncRoot
=> this;
1060 bool IDictionary
.IsFixedSize
=> false;
1062 bool IDictionary
.IsReadOnly
=> false;
1064 ICollection IDictionary
.Keys
=> (ICollection
)Keys
;
1066 ICollection IDictionary
.Values
=> (ICollection
)Values
;
1068 object? IDictionary
.this[object key
]
1072 if (IsCompatibleKey(key
))
1074 int i
= FindEntry((TKey
)key
);
1077 return _entries
![i
].value;
1086 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1088 ThrowHelper
.IfNullAndNullsAreIllegalThenThrow
<TValue
>(value, ExceptionArgument
.value);
1092 TKey tempKey
= (TKey
)key
;
1095 this[tempKey
] = (TValue
)value!;
1097 catch (InvalidCastException
)
1099 ThrowHelper
.ThrowWrongValueTypeArgumentException(value, typeof(TValue
));
1102 catch (InvalidCastException
)
1104 ThrowHelper
.ThrowWrongKeyTypeArgumentException(key
, typeof(TKey
));
1109 private static bool IsCompatibleKey(object key
)
1113 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1115 return (key
is TKey
);
1118 void IDictionary
.Add(object key
, object? value)
1122 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1124 ThrowHelper
.IfNullAndNullsAreIllegalThenThrow
<TValue
>(value, ExceptionArgument
.value);
1128 TKey tempKey
= (TKey
)key
;
1132 Add(tempKey
, (TValue
)value!);
1134 catch (InvalidCastException
)
1136 ThrowHelper
.ThrowWrongValueTypeArgumentException(value, typeof(TValue
));
1139 catch (InvalidCastException
)
1141 ThrowHelper
.ThrowWrongKeyTypeArgumentException(key
, typeof(TKey
));
1145 bool IDictionary
.Contains(object key
)
1147 if (IsCompatibleKey(key
))
1149 return ContainsKey((TKey
)key
);
1155 IDictionaryEnumerator IDictionary
.GetEnumerator()
1156 => new Enumerator(this, Enumerator
.DictEntry
);
1158 void IDictionary
.Remove(object key
)
1160 if (IsCompatibleKey(key
))
1166 public struct Enumerator
: IEnumerator
<KeyValuePair
<TKey
, TValue
>>,
1167 IDictionaryEnumerator
1169 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1170 private readonly int _version
;
1172 private KeyValuePair
<TKey
, TValue
> _current
;
1173 private readonly int _getEnumeratorRetType
; // What should Enumerator.Current return?
1175 internal const int DictEntry
= 1;
1176 internal const int KeyValuePair
= 2;
1178 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
, int getEnumeratorRetType
)
1180 _dictionary
= dictionary
;
1181 _version
= dictionary
._version
;
1183 _getEnumeratorRetType
= getEnumeratorRetType
;
1184 _current
= new KeyValuePair
<TKey
, TValue
>();
1187 public bool MoveNext()
1189 if (_version
!= _dictionary
._version
)
1191 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1194 // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
1195 // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
1196 while ((uint)_index
< (uint)_dictionary
._count
)
1198 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1200 if (entry
.next
>= -1)
1202 _current
= new KeyValuePair
<TKey
, TValue
>(entry
.key
, entry
.value);
1207 _index
= _dictionary
._count
+ 1;
1208 _current
= new KeyValuePair
<TKey
, TValue
>();
1212 public KeyValuePair
<TKey
, TValue
> Current
=> _current
;
1214 public void Dispose()
1218 object? IEnumerator
.Current
1222 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1224 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1227 if (_getEnumeratorRetType
== DictEntry
)
1229 return new DictionaryEntry(_current
.Key
, _current
.Value
);
1233 return new KeyValuePair
<TKey
, TValue
>(_current
.Key
, _current
.Value
);
1238 void IEnumerator
.Reset()
1240 if (_version
!= _dictionary
._version
)
1242 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1246 _current
= new KeyValuePair
<TKey
, TValue
>();
1249 DictionaryEntry IDictionaryEnumerator
.Entry
1253 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1255 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1258 return new DictionaryEntry(_current
.Key
, _current
.Value
);
1262 object IDictionaryEnumerator
.Key
1266 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1268 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1271 return _current
.Key
;
1275 object? IDictionaryEnumerator
.Value
1279 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1281 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1284 return _current
.Value
;
1289 [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView
<,>))]
1290 [DebuggerDisplay("Count = {Count}")]
1291 public sealed class KeyCollection
: ICollection
<TKey
>, ICollection
, IReadOnlyCollection
<TKey
>
1293 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1295 public KeyCollection(Dictionary
<TKey
, TValue
> dictionary
)
1297 if (dictionary
== null)
1299 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.dictionary
);
1301 _dictionary
= dictionary
;
1304 public Enumerator
GetEnumerator()
1305 => new Enumerator(_dictionary
);
1307 public void CopyTo(TKey
[] array
, int index
)
1311 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1314 if (index
< 0 || index
> array
.Length
)
1316 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1319 if (array
.Length
- index
< _dictionary
.Count
)
1321 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1324 int count
= _dictionary
._count
;
1325 Entry
[]? entries
= _dictionary
._entries
;
1326 for (int i
= 0; i
< count
; i
++)
1328 if (entries
![i
].next
>= -1) array
[index
++] = entries
[i
].key
;
1332 public int Count
=> _dictionary
.Count
;
1334 bool ICollection
<TKey
>.IsReadOnly
=> true;
1336 void ICollection
<TKey
>.Add(TKey item
)
1337 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1339 void ICollection
<TKey
>.Clear()
1340 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1342 bool ICollection
<TKey
>.Contains(TKey item
)
1343 => _dictionary
.ContainsKey(item
);
1345 bool ICollection
<TKey
>.Remove(TKey item
)
1347 ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1351 IEnumerator
<TKey
> IEnumerable
<TKey
>.GetEnumerator()
1352 => new Enumerator(_dictionary
);
1354 IEnumerator IEnumerable
.GetEnumerator()
1355 => new Enumerator(_dictionary
);
1357 void ICollection
.CopyTo(Array array
, int index
)
1360 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1361 if (array
.Rank
!= 1)
1362 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
1363 if (array
.GetLowerBound(0) != 0)
1364 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
1365 if ((uint)index
> (uint)array
.Length
)
1366 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1367 if (array
.Length
- index
< _dictionary
.Count
)
1368 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1370 if (array
is TKey
[] keys
)
1372 CopyTo(keys
, index
);
1376 object[]? objects
= array
as object[];
1377 if (objects
== null)
1379 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1382 int count
= _dictionary
._count
;
1383 Entry
[]? entries
= _dictionary
._entries
;
1386 for (int i
= 0; i
< count
; i
++)
1388 if (entries
![i
].next
>= -1) objects
[index
++] = entries
[i
].key
;
1391 catch (ArrayTypeMismatchException
)
1393 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1398 bool ICollection
.IsSynchronized
=> false;
1400 object ICollection
.SyncRoot
=> ((ICollection
)_dictionary
).SyncRoot
;
1402 public struct Enumerator
: IEnumerator
<TKey
>, IEnumerator
1404 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1406 private readonly int _version
;
1407 [AllowNull
, MaybeNull
] private TKey _currentKey
;
1409 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
)
1411 _dictionary
= dictionary
;
1412 _version
= dictionary
._version
;
1414 _currentKey
= default;
1417 public void Dispose()
1421 public bool MoveNext()
1423 if (_version
!= _dictionary
._version
)
1425 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1428 while ((uint)_index
< (uint)_dictionary
._count
)
1430 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1432 if (entry
.next
>= -1)
1434 _currentKey
= entry
.key
;
1439 _index
= _dictionary
._count
+ 1;
1440 _currentKey
= default;
1444 public TKey Current
=> _currentKey
!;
1446 object? IEnumerator
.Current
1450 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1452 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1459 void IEnumerator
.Reset()
1461 if (_version
!= _dictionary
._version
)
1463 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1467 _currentKey
= default;
1472 [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView
<,>))]
1473 [DebuggerDisplay("Count = {Count}")]
1474 public sealed class ValueCollection
: ICollection
<TValue
>, ICollection
, IReadOnlyCollection
<TValue
>
1476 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1478 public ValueCollection(Dictionary
<TKey
, TValue
> dictionary
)
1480 if (dictionary
== null)
1482 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.dictionary
);
1484 _dictionary
= dictionary
;
1487 public Enumerator
GetEnumerator()
1488 => new Enumerator(_dictionary
);
1490 public void CopyTo(TValue
[] array
, int index
)
1494 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1497 if ((uint)index
> array
.Length
)
1499 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1502 if (array
.Length
- index
< _dictionary
.Count
)
1504 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1507 int count
= _dictionary
._count
;
1508 Entry
[]? entries
= _dictionary
._entries
;
1509 for (int i
= 0; i
< count
; i
++)
1511 if (entries
![i
].next
>= -1) array
[index
++] = entries
[i
].value;
1515 public int Count
=> _dictionary
.Count
;
1517 bool ICollection
<TValue
>.IsReadOnly
=> true;
1519 void ICollection
<TValue
>.Add(TValue item
)
1520 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1522 bool ICollection
<TValue
>.Remove(TValue item
)
1524 ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1528 void ICollection
<TValue
>.Clear()
1529 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1531 bool ICollection
<TValue
>.Contains(TValue item
)
1532 => _dictionary
.ContainsValue(item
);
1534 IEnumerator
<TValue
> IEnumerable
<TValue
>.GetEnumerator()
1535 => new Enumerator(_dictionary
);
1537 IEnumerator IEnumerable
.GetEnumerator()
1538 => new Enumerator(_dictionary
);
1540 void ICollection
.CopyTo(Array array
, int index
)
1543 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1544 if (array
.Rank
!= 1)
1545 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
1546 if (array
.GetLowerBound(0) != 0)
1547 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
1548 if ((uint)index
> (uint)array
.Length
)
1549 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1550 if (array
.Length
- index
< _dictionary
.Count
)
1551 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1553 if (array
is TValue
[] values
)
1555 CopyTo(values
, index
);
1559 object[]? objects
= array
as object[];
1560 if (objects
== null)
1562 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1565 int count
= _dictionary
._count
;
1566 Entry
[]? entries
= _dictionary
._entries
;
1569 for (int i
= 0; i
< count
; i
++)
1571 if (entries
![i
].next
>= -1) objects
[index
++] = entries
[i
].value!;
1574 catch (ArrayTypeMismatchException
)
1576 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1581 bool ICollection
.IsSynchronized
=> false;
1583 object ICollection
.SyncRoot
=> ((ICollection
)_dictionary
).SyncRoot
;
1585 public struct Enumerator
: IEnumerator
<TValue
>, IEnumerator
1587 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1589 private readonly int _version
;
1590 [AllowNull
, MaybeNull
] private TValue _currentValue
;
1592 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
)
1594 _dictionary
= dictionary
;
1595 _version
= dictionary
._version
;
1597 _currentValue
= default;
1600 public void Dispose()
1604 public bool MoveNext()
1606 if (_version
!= _dictionary
._version
)
1608 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1611 while ((uint)_index
< (uint)_dictionary
._count
)
1613 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1615 if (entry
.next
>= -1)
1617 _currentValue
= entry
.value;
1621 _index
= _dictionary
._count
+ 1;
1622 _currentValue
= default;
1626 public TValue Current
=> _currentValue
!;
1628 object? IEnumerator
.Current
1632 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1634 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1637 return _currentValue
;
1641 void IEnumerator
.Reset()
1643 if (_version
!= _dictionary
._version
)
1645 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1648 _currentValue
= default;