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
=>
149 (_comparer
== null ||_comparer
is NonRandomizedStringEqualityComparer
) ?
150 EqualityComparer
<TKey
>.Default
:
153 public int Count
=> _count
- _freeCount
;
155 public KeyCollection Keys
159 if (_keys
== null) _keys
= new KeyCollection(this);
164 ICollection
<TKey
> IDictionary
<TKey
, TValue
>.Keys
168 if (_keys
== null) _keys
= new KeyCollection(this);
173 IEnumerable
<TKey
> IReadOnlyDictionary
<TKey
, TValue
>.Keys
177 if (_keys
== null) _keys
= new KeyCollection(this);
182 public ValueCollection Values
186 if (_values
== null) _values
= new ValueCollection(this);
191 ICollection
<TValue
> IDictionary
<TKey
, TValue
>.Values
195 if (_values
== null) _values
= new ValueCollection(this);
200 IEnumerable
<TValue
> IReadOnlyDictionary
<TKey
, TValue
>.Values
204 if (_values
== null) _values
= new ValueCollection(this);
209 public TValue
this[TKey key
]
213 int i
= FindEntry(key
);
214 if (i
>= 0) return _entries
![i
].value;
215 ThrowHelper
.ThrowKeyNotFoundException(key
);
220 bool modified
= TryInsert(key
, value, InsertionBehavior
.OverwriteExisting
);
221 Debug
.Assert(modified
);
225 public void Add(TKey key
, TValue
value)
227 bool modified
= TryInsert(key
, value, InsertionBehavior
.ThrowOnExisting
);
228 Debug
.Assert(modified
); // If there was an existing key and the Add failed, an exception will already have been thrown.
231 void ICollection
<KeyValuePair
<TKey
, TValue
>>.Add(KeyValuePair
<TKey
, TValue
> keyValuePair
)
232 => Add(keyValuePair
.Key
, keyValuePair
.Value
);
234 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.Contains(KeyValuePair
<TKey
, TValue
> keyValuePair
)
236 int i
= FindEntry(keyValuePair
.Key
);
237 if (i
>= 0 && EqualityComparer
<TValue
>.Default
.Equals(_entries
![i
].value, keyValuePair
.Value
))
244 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.Remove(KeyValuePair
<TKey
, TValue
> keyValuePair
)
246 int i
= FindEntry(keyValuePair
.Key
);
247 if (i
>= 0 && EqualityComparer
<TValue
>.Default
.Equals(_entries
![i
].value, keyValuePair
.Value
))
249 Remove(keyValuePair
.Key
);
260 Debug
.Assert(_buckets
!= null, "_buckets should be non-null");
261 Debug
.Assert(_entries
!= null, "_entries should be non-null");
263 Array
.Clear(_buckets
, 0, _buckets
.Length
);
268 Array
.Clear(_entries
, 0, count
);
272 public bool ContainsKey(TKey key
)
273 => FindEntry(key
) >= 0;
275 public bool ContainsValue(TValue
value)
277 Entry
[]? entries
= _entries
;
280 for (int i
= 0; i
< _count
; i
++)
282 if (entries
![i
].next
>= -1 && entries
[i
].value == null) return true;
287 if (default(TValue
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
289 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
290 for (int i
= 0; i
< _count
; i
++)
292 if (entries
![i
].next
>= -1 && EqualityComparer
<TValue
>.Default
.Equals(entries
[i
].value, value)) return true;
297 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
298 // https://github.com/dotnet/coreclr/issues/17273
299 // So cache in a local rather than get EqualityComparer per loop iteration
300 EqualityComparer
<TValue
> defaultComparer
= EqualityComparer
<TValue
>.Default
;
301 for (int i
= 0; i
< _count
; i
++)
303 if (entries
![i
].next
>= -1 && defaultComparer
.Equals(entries
[i
].value, value)) return true;
310 private void CopyTo(KeyValuePair
<TKey
, TValue
>[] array
, int index
)
314 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
317 if ((uint)index
> (uint)array
.Length
)
319 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
322 if (array
.Length
- index
< Count
)
324 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
328 Entry
[]? entries
= _entries
;
329 for (int i
= 0; i
< count
; i
++)
331 if (entries
![i
].next
>= -1)
333 array
[index
++] = new KeyValuePair
<TKey
, TValue
>(entries
[i
].key
, entries
[i
].value);
338 public Enumerator
GetEnumerator()
339 => new Enumerator(this, Enumerator
.KeyValuePair
);
341 IEnumerator
<KeyValuePair
<TKey
, TValue
>> IEnumerable
<KeyValuePair
<TKey
, TValue
>>.GetEnumerator()
342 => new Enumerator(this, Enumerator
.KeyValuePair
);
344 public virtual void GetObjectData(SerializationInfo info
, StreamingContext context
)
348 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.info
);
351 info
.AddValue(VersionName
, _version
);
352 info
.AddValue(ComparerName
, _comparer
?? EqualityComparer
<TKey
>.Default
, typeof(IEqualityComparer
<TKey
>));
353 info
.AddValue(HashSizeName
, _buckets
== null ? 0 : _buckets
.Length
); // This is the length of the bucket array
355 if (_buckets
!= null)
357 var array
= new KeyValuePair
<TKey
, TValue
>[Count
];
359 info
.AddValue(KeyValuePairsName
, array
, typeof(KeyValuePair
<TKey
, TValue
>[]));
363 private int FindEntry(TKey key
)
367 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
371 int[]? buckets
= _buckets
;
372 Entry
[]? entries
= _entries
;
373 int collisionCount
= 0;
376 Debug
.Assert(entries
!= null, "expected entries to be != null");
377 IEqualityComparer
<TKey
>? comparer
= _comparer
;
378 if (comparer
== null)
380 uint hashCode
= (uint)key
.GetHashCode();
381 // Value in _buckets is 1-based
382 i
= buckets
[hashCode
% (uint)buckets
.Length
] - 1;
383 if (default(TKey
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
385 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
388 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
389 // Test in if to drop range check for following array access
390 if ((uint)i
>= (uint)entries
.Length
|| (entries
[i
].hashCode
== hashCode
&& EqualityComparer
<TKey
>.Default
.Equals(entries
[i
].key
, key
)))
396 if (collisionCount
>= entries
.Length
)
398 // The chain of entries forms a loop; which means a concurrent update has happened.
399 // Break out of the loop and throw, rather than looping forever.
400 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
407 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
408 // https://github.com/dotnet/coreclr/issues/17273
409 // So cache in a local rather than get EqualityComparer per loop iteration
410 EqualityComparer
<TKey
> defaultComparer
= EqualityComparer
<TKey
>.Default
;
413 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
414 // Test in if to drop range check for following array access
415 if ((uint)i
>= (uint)entries
.Length
|| (entries
[i
].hashCode
== hashCode
&& defaultComparer
.Equals(entries
[i
].key
, key
)))
421 if (collisionCount
>= entries
.Length
)
423 // The chain of entries forms a loop; which means a concurrent update has happened.
424 // Break out of the loop and throw, rather than looping forever.
425 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
433 uint hashCode
= (uint)comparer
.GetHashCode(key
);
434 // Value in _buckets is 1-based
435 i
= buckets
[hashCode
% (uint)buckets
.Length
] - 1;
438 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
439 // Test in if to drop range check for following array access
440 if ((uint)i
>= (uint)entries
.Length
||
441 (entries
[i
].hashCode
== hashCode
&& comparer
.Equals(entries
[i
].key
, key
)))
447 if (collisionCount
>= entries
.Length
)
449 // The chain of entries forms a loop; which means a concurrent update has happened.
450 // Break out of the loop and throw, rather than looping forever.
451 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
461 private int Initialize(int capacity
)
463 int size
= HashHelpers
.GetPrime(capacity
);
466 _buckets
= new int[size
];
467 _entries
= new Entry
[size
];
472 private bool TryInsert(TKey key
, TValue
value, InsertionBehavior behavior
)
476 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
479 if (_buckets
== null)
483 Debug
.Assert(_buckets
!= null);
485 Entry
[]? entries
= _entries
;
486 Debug
.Assert(entries
!= null, "expected entries to be non-null");
488 IEqualityComparer
<TKey
>? comparer
= _comparer
;
489 uint hashCode
= (uint)((comparer
== null) ? key
.GetHashCode() : comparer
.GetHashCode(key
));
491 int collisionCount
= 0;
492 ref int bucket
= ref _buckets
[hashCode
% (uint)_buckets
.Length
];
493 // Value in _buckets is 1-based
496 if (comparer
== null)
498 if (default(TKey
)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
500 // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
503 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
504 // Test uint in if rather than loop condition to drop range check for following array access
505 if ((uint)i
>= (uint)entries
.Length
)
510 if (entries
[i
].hashCode
== hashCode
&& EqualityComparer
<TKey
>.Default
.Equals(entries
[i
].key
, key
))
512 if (behavior
== InsertionBehavior
.OverwriteExisting
)
514 entries
[i
].value = value;
519 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
521 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
528 if (collisionCount
>= entries
.Length
)
530 // The chain of entries forms a loop; which means a concurrent update has happened.
531 // Break out of the loop and throw, rather than looping forever.
532 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
539 // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
540 // https://github.com/dotnet/coreclr/issues/17273
541 // So cache in a local rather than get EqualityComparer per loop iteration
542 EqualityComparer
<TKey
> defaultComparer
= EqualityComparer
<TKey
>.Default
;
545 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
546 // Test uint in if rather than loop condition to drop range check for following array access
547 if ((uint)i
>= (uint)entries
.Length
)
552 if (entries
[i
].hashCode
== hashCode
&& defaultComparer
.Equals(entries
[i
].key
, key
))
554 if (behavior
== InsertionBehavior
.OverwriteExisting
)
556 entries
[i
].value = value;
561 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
563 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
570 if (collisionCount
>= entries
.Length
)
572 // The chain of entries forms a loop; which means a concurrent update has happened.
573 // Break out of the loop and throw, rather than looping forever.
574 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
584 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
585 // Test uint in if rather than loop condition to drop range check for following array access
586 if ((uint)i
>= (uint)entries
.Length
)
591 if (entries
[i
].hashCode
== hashCode
&& comparer
.Equals(entries
[i
].key
, key
))
593 if (behavior
== InsertionBehavior
.OverwriteExisting
)
595 entries
[i
].value = value;
600 if (behavior
== InsertionBehavior
.ThrowOnExisting
)
602 ThrowHelper
.ThrowAddingDuplicateWithKeyArgumentException(key
);
609 if (collisionCount
>= entries
.Length
)
611 // The chain of entries forms a loop; which means a concurrent update has happened.
612 // Break out of the loop and throw, rather than looping forever.
613 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
620 bool updateFreeList
= false;
625 updateFreeList
= true;
631 if (count
== entries
.Length
)
634 bucket
= ref _buckets
[hashCode
% (uint)_buckets
.Length
];
641 ref Entry entry
= ref entries
![index
];
645 Debug
.Assert((StartOfFreeList
- entries
[_freeList
].next
) >= -1, "shouldn't overflow because `next` cannot underflow");
647 _freeList
= StartOfFreeList
- entries
[_freeList
].next
;
649 entry
.hashCode
= hashCode
;
650 // Value in _buckets is 1-based
651 entry
.next
= bucket
- 1;
654 // Value in _buckets is 1-based
658 // Value types never rehash
659 if (default(TKey
)! == null && collisionCount
> HashHelpers
.HashCollisionThreshold
&& comparer
is NonRandomizedStringEqualityComparer
) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
661 // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
662 // i.e. EqualityComparer<string>.Default.
664 Resize(entries
.Length
, true);
670 public virtual void OnDeserialization(object? sender
)
672 HashHelpers
.SerializationInfoTable
.TryGetValue(this, out SerializationInfo
? siInfo
);
676 // We can return immediately if this function is called twice.
677 // Note we remove the serialization info from the table at the end of this method.
681 int realVersion
= siInfo
.GetInt32(VersionName
);
682 int hashsize
= siInfo
.GetInt32(HashSizeName
);
683 _comparer
= (IEqualityComparer
<TKey
>)siInfo
.GetValue(ComparerName
, typeof(IEqualityComparer
<TKey
>))!; // When serialized if comparer is null, we use the default.
687 Initialize(hashsize
);
689 KeyValuePair
<TKey
, TValue
>[]? array
= (KeyValuePair
<TKey
, TValue
>[]?)
690 siInfo
.GetValue(KeyValuePairsName
, typeof(KeyValuePair
<TKey
, TValue
>[]));
694 ThrowHelper
.ThrowSerializationException(ExceptionResource
.Serialization_MissingKeys
);
697 for (int i
= 0; i
< array
.Length
; i
++)
699 if (array
[i
].Key
== null)
701 ThrowHelper
.ThrowSerializationException(ExceptionResource
.Serialization_NullKey
);
703 Add(array
[i
].Key
, array
[i
].Value
);
711 _version
= realVersion
;
712 HashHelpers
.SerializationInfoTable
.Remove(this);
715 private void Resize()
716 => Resize(HashHelpers
.ExpandPrime(_count
), false);
718 private void Resize(int newSize
, bool forceNewHashCodes
)
720 // Value types never rehash
721 Debug
.Assert(!forceNewHashCodes
|| default(TKey
)! == null); // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
722 Debug
.Assert(_entries
!= null, "_entries should be non-null");
723 Debug
.Assert(newSize
>= _entries
.Length
);
725 int[] buckets
= new int[newSize
];
726 Entry
[] entries
= new Entry
[newSize
];
729 Array
.Copy(_entries
, 0, entries
, 0, count
);
731 if (default(TKey
)! == null && forceNewHashCodes
) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
733 for (int i
= 0; i
< count
; i
++)
735 if (entries
[i
].next
>= -1)
737 Debug
.Assert(_comparer
== null);
738 entries
[i
].hashCode
= (uint)entries
[i
].key
.GetHashCode();
743 for (int i
= 0; i
< count
; i
++)
745 if (entries
[i
].next
>= -1)
747 uint bucket
= entries
[i
].hashCode
% (uint)newSize
;
748 // Value in _buckets is 1-based
749 entries
[i
].next
= buckets
[bucket
] - 1;
750 // Value in _buckets is 1-based
751 buckets
[bucket
] = i
+ 1;
759 // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
760 // statement to copy the value for entry being removed into the output parameter.
761 // Code has been intentionally duplicated for performance reasons.
762 public bool Remove(TKey key
)
766 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
769 int[]? buckets
= _buckets
;
770 Entry
[]? entries
= _entries
;
771 int collisionCount
= 0;
774 Debug
.Assert(entries
!= null, "entries should be non-null");
775 uint hashCode
= (uint)(_comparer
?.GetHashCode(key
) ?? key
.GetHashCode());
776 uint bucket
= hashCode
% (uint)buckets
.Length
;
778 // Value in buckets is 1-based
779 int i
= buckets
[bucket
] - 1;
782 ref Entry entry
= ref entries
[i
];
784 if (entry
.hashCode
== hashCode
&& (_comparer
?.Equals(entry
.key
, key
) ?? EqualityComparer
<TKey
>.Default
.Equals(entry
.key
, key
)))
788 // Value in buckets is 1-based
789 buckets
[bucket
] = entry
.next
+ 1;
793 entries
[last
].next
= entry
.next
;
796 Debug
.Assert((StartOfFreeList
- _freeList
) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
798 entry
.next
= StartOfFreeList
- _freeList
;
800 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TKey
>())
802 entry
.key
= default!;
804 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TValue
>())
806 entry
.value = default!;
815 if (collisionCount
>= entries
.Length
)
817 // The chain of entries forms a loop; which means a concurrent update has happened.
818 // Break out of the loop and throw, rather than looping forever.
819 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
827 // This overload is a copy of the overload Remove(TKey key) with one additional
828 // statement to copy the value for entry being removed into the output parameter.
829 // Code has been intentionally duplicated for performance reasons.
830 public bool Remove(TKey key
, [MaybeNullWhen(false)] out TValue
value)
834 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
837 int[]? buckets
= _buckets
;
838 Entry
[]? entries
= _entries
;
839 int collisionCount
= 0;
842 Debug
.Assert(entries
!= null, "entries should be non-null");
843 uint hashCode
= (uint)(_comparer
?.GetHashCode(key
) ?? key
.GetHashCode());
844 uint bucket
= hashCode
% (uint)buckets
.Length
;
846 // Value in buckets is 1-based
847 int i
= buckets
[bucket
] - 1;
850 ref Entry entry
= ref entries
[i
];
852 if (entry
.hashCode
== hashCode
&& (_comparer
?.Equals(entry
.key
, key
) ?? EqualityComparer
<TKey
>.Default
.Equals(entry
.key
, key
)))
856 // Value in buckets is 1-based
857 buckets
[bucket
] = entry
.next
+ 1;
861 entries
[last
].next
= entry
.next
;
866 Debug
.Assert((StartOfFreeList
- _freeList
) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
868 entry
.next
= StartOfFreeList
- _freeList
;
870 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TKey
>())
872 entry
.key
= default!;
874 if (RuntimeHelpers
.IsReferenceOrContainsReferences
<TValue
>())
876 entry
.value = default!;
885 if (collisionCount
>= entries
.Length
)
887 // The chain of entries forms a loop; which means a concurrent update has happened.
888 // Break out of the loop and throw, rather than looping forever.
889 ThrowHelper
.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
898 public bool TryGetValue(TKey key
, [MaybeNullWhen(false)] out TValue
value)
900 int i
= FindEntry(key
);
903 value = _entries
![i
].value;
910 public bool TryAdd(TKey key
, TValue
value)
911 => TryInsert(key
, value, InsertionBehavior
.None
);
913 bool ICollection
<KeyValuePair
<TKey
, TValue
>>.IsReadOnly
=> false;
915 void ICollection
<KeyValuePair
<TKey
, TValue
>>.CopyTo(KeyValuePair
<TKey
, TValue
>[] array
, int index
)
916 => CopyTo(array
, index
);
918 void ICollection
.CopyTo(Array array
, int index
)
921 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
923 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
924 if (array
.GetLowerBound(0) != 0)
925 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
926 if ((uint)index
> (uint)array
.Length
)
927 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
928 if (array
.Length
- index
< Count
)
929 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
931 if (array
is KeyValuePair
<TKey
, TValue
>[] pairs
)
933 CopyTo(pairs
, index
);
935 else if (array
is DictionaryEntry
[] dictEntryArray
)
937 Entry
[]? entries
= _entries
;
938 for (int i
= 0; i
< _count
; i
++)
940 if (entries
![i
].next
>= -1)
942 dictEntryArray
[index
++] = new DictionaryEntry(entries
[i
].key
, entries
[i
].value);
948 object[]? objects
= array
as object[];
951 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
957 Entry
[]? entries
= _entries
;
958 for (int i
= 0; i
< count
; i
++)
960 if (entries
![i
].next
>= -1)
962 objects
[index
++] = new KeyValuePair
<TKey
, TValue
>(entries
[i
].key
, entries
[i
].value);
966 catch (ArrayTypeMismatchException
)
968 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
973 IEnumerator IEnumerable
.GetEnumerator()
974 => new Enumerator(this, Enumerator
.KeyValuePair
);
977 /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
979 public int EnsureCapacity(int capacity
)
982 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.capacity
);
983 int currentCapacity
= _entries
== null ? 0 : _entries
.Length
;
984 if (currentCapacity
>= capacity
)
985 return currentCapacity
;
987 if (_buckets
== null)
988 return Initialize(capacity
);
989 int newSize
= HashHelpers
.GetPrime(capacity
);
990 Resize(newSize
, forceNewHashCodes
: false);
995 /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
997 /// This method can be used to minimize the memory overhead
998 /// once it is known that no new elements will be added.
1000 /// To allocate minimum size storage array, execute the following statements:
1002 /// dictionary.Clear();
1003 /// dictionary.TrimExcess();
1005 public void TrimExcess()
1006 => TrimExcess(Count
);
1009 /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
1011 /// This method can be used to minimize the memory overhead
1012 /// once it is known that no new elements will be added.
1014 public void TrimExcess(int capacity
)
1016 if (capacity
< Count
)
1017 ThrowHelper
.ThrowArgumentOutOfRangeException(ExceptionArgument
.capacity
);
1018 int newSize
= HashHelpers
.GetPrime(capacity
);
1020 Entry
[]? oldEntries
= _entries
;
1021 int currentCapacity
= oldEntries
== null ? 0 : oldEntries
.Length
;
1022 if (newSize
>= currentCapacity
)
1025 int oldCount
= _count
;
1027 Initialize(newSize
);
1028 Entry
[]? entries
= _entries
;
1029 int[]? buckets
= _buckets
;
1031 for (int i
= 0; i
< oldCount
; i
++)
1033 uint hashCode
= oldEntries
![i
].hashCode
; // At this point, we know we have entries.
1034 if (oldEntries
[i
].next
>= -1)
1036 ref Entry entry
= ref entries
![count
];
1037 entry
= oldEntries
[i
];
1038 uint bucket
= hashCode
% (uint)newSize
;
1039 // Value in _buckets is 1-based
1040 entry
.next
= buckets
![bucket
] - 1; // If we get here, we have entries, therefore buckets is not null.
1041 // Value in _buckets is 1-based
1042 buckets
[bucket
] = count
+ 1;
1050 bool ICollection
.IsSynchronized
=> false;
1052 object ICollection
.SyncRoot
=> this;
1054 bool IDictionary
.IsFixedSize
=> false;
1056 bool IDictionary
.IsReadOnly
=> false;
1058 ICollection IDictionary
.Keys
=> (ICollection
)Keys
;
1060 ICollection IDictionary
.Values
=> (ICollection
)Values
;
1062 object? IDictionary
.this[object key
]
1066 if (IsCompatibleKey(key
))
1068 int i
= FindEntry((TKey
)key
);
1071 return _entries
![i
].value;
1080 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1082 ThrowHelper
.IfNullAndNullsAreIllegalThenThrow
<TValue
>(value, ExceptionArgument
.value);
1086 TKey tempKey
= (TKey
)key
;
1089 this[tempKey
] = (TValue
)value!;
1091 catch (InvalidCastException
)
1093 ThrowHelper
.ThrowWrongValueTypeArgumentException(value, typeof(TValue
));
1096 catch (InvalidCastException
)
1098 ThrowHelper
.ThrowWrongKeyTypeArgumentException(key
, typeof(TKey
));
1103 private static bool IsCompatibleKey(object key
)
1107 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1109 return (key
is TKey
);
1112 void IDictionary
.Add(object key
, object? value)
1116 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.key
);
1118 ThrowHelper
.IfNullAndNullsAreIllegalThenThrow
<TValue
>(value, ExceptionArgument
.value);
1122 TKey tempKey
= (TKey
)key
;
1126 Add(tempKey
, (TValue
)value!);
1128 catch (InvalidCastException
)
1130 ThrowHelper
.ThrowWrongValueTypeArgumentException(value, typeof(TValue
));
1133 catch (InvalidCastException
)
1135 ThrowHelper
.ThrowWrongKeyTypeArgumentException(key
, typeof(TKey
));
1139 bool IDictionary
.Contains(object key
)
1141 if (IsCompatibleKey(key
))
1143 return ContainsKey((TKey
)key
);
1149 IDictionaryEnumerator IDictionary
.GetEnumerator()
1150 => new Enumerator(this, Enumerator
.DictEntry
);
1152 void IDictionary
.Remove(object key
)
1154 if (IsCompatibleKey(key
))
1160 public struct Enumerator
: IEnumerator
<KeyValuePair
<TKey
, TValue
>>,
1161 IDictionaryEnumerator
1163 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1164 private readonly int _version
;
1166 private KeyValuePair
<TKey
, TValue
> _current
;
1167 private readonly int _getEnumeratorRetType
; // What should Enumerator.Current return?
1169 internal const int DictEntry
= 1;
1170 internal const int KeyValuePair
= 2;
1172 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
, int getEnumeratorRetType
)
1174 _dictionary
= dictionary
;
1175 _version
= dictionary
._version
;
1177 _getEnumeratorRetType
= getEnumeratorRetType
;
1178 _current
= new KeyValuePair
<TKey
, TValue
>();
1181 public bool MoveNext()
1183 if (_version
!= _dictionary
._version
)
1185 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1188 // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
1189 // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
1190 while ((uint)_index
< (uint)_dictionary
._count
)
1192 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1194 if (entry
.next
>= -1)
1196 _current
= new KeyValuePair
<TKey
, TValue
>(entry
.key
, entry
.value);
1201 _index
= _dictionary
._count
+ 1;
1202 _current
= new KeyValuePair
<TKey
, TValue
>();
1206 public KeyValuePair
<TKey
, TValue
> Current
=> _current
;
1208 public void Dispose()
1212 object? IEnumerator
.Current
1216 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1218 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1221 if (_getEnumeratorRetType
== DictEntry
)
1223 return new DictionaryEntry(_current
.Key
, _current
.Value
);
1227 return new KeyValuePair
<TKey
, TValue
>(_current
.Key
, _current
.Value
);
1232 void IEnumerator
.Reset()
1234 if (_version
!= _dictionary
._version
)
1236 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1240 _current
= new KeyValuePair
<TKey
, TValue
>();
1243 DictionaryEntry IDictionaryEnumerator
.Entry
1247 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1249 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1252 return new DictionaryEntry(_current
.Key
, _current
.Value
);
1256 object IDictionaryEnumerator
.Key
1260 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1262 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1265 return _current
.Key
;
1269 object? IDictionaryEnumerator
.Value
1273 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1275 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1278 return _current
.Value
;
1283 [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView
<,>))]
1284 [DebuggerDisplay("Count = {Count}")]
1285 public sealed class KeyCollection
: ICollection
<TKey
>, ICollection
, IReadOnlyCollection
<TKey
>
1287 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1289 public KeyCollection(Dictionary
<TKey
, TValue
> dictionary
)
1291 if (dictionary
== null)
1293 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.dictionary
);
1295 _dictionary
= dictionary
;
1298 public Enumerator
GetEnumerator()
1299 => new Enumerator(_dictionary
);
1301 public void CopyTo(TKey
[] array
, int index
)
1305 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1308 if (index
< 0 || index
> array
.Length
)
1310 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1313 if (array
.Length
- index
< _dictionary
.Count
)
1315 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1318 int count
= _dictionary
._count
;
1319 Entry
[]? entries
= _dictionary
._entries
;
1320 for (int i
= 0; i
< count
; i
++)
1322 if (entries
![i
].next
>= -1) array
[index
++] = entries
[i
].key
;
1326 public int Count
=> _dictionary
.Count
;
1328 bool ICollection
<TKey
>.IsReadOnly
=> true;
1330 void ICollection
<TKey
>.Add(TKey item
)
1331 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1333 void ICollection
<TKey
>.Clear()
1334 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1336 bool ICollection
<TKey
>.Contains(TKey item
)
1337 => _dictionary
.ContainsKey(item
);
1339 bool ICollection
<TKey
>.Remove(TKey item
)
1341 ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_KeyCollectionSet
);
1345 IEnumerator
<TKey
> IEnumerable
<TKey
>.GetEnumerator()
1346 => new Enumerator(_dictionary
);
1348 IEnumerator IEnumerable
.GetEnumerator()
1349 => new Enumerator(_dictionary
);
1351 void ICollection
.CopyTo(Array array
, int index
)
1354 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1355 if (array
.Rank
!= 1)
1356 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
1357 if (array
.GetLowerBound(0) != 0)
1358 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
1359 if ((uint)index
> (uint)array
.Length
)
1360 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1361 if (array
.Length
- index
< _dictionary
.Count
)
1362 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1364 if (array
is TKey
[] keys
)
1366 CopyTo(keys
, index
);
1370 object[]? objects
= array
as object[];
1371 if (objects
== null)
1373 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1376 int count
= _dictionary
._count
;
1377 Entry
[]? entries
= _dictionary
._entries
;
1380 for (int i
= 0; i
< count
; i
++)
1382 if (entries
![i
].next
>= -1) objects
[index
++] = entries
[i
].key
;
1385 catch (ArrayTypeMismatchException
)
1387 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1392 bool ICollection
.IsSynchronized
=> false;
1394 object ICollection
.SyncRoot
=> ((ICollection
)_dictionary
).SyncRoot
;
1396 public struct Enumerator
: IEnumerator
<TKey
>, IEnumerator
1398 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1400 private readonly int _version
;
1401 [AllowNull
, MaybeNull
] private TKey _currentKey
;
1403 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
)
1405 _dictionary
= dictionary
;
1406 _version
= dictionary
._version
;
1408 _currentKey
= default;
1411 public void Dispose()
1415 public bool MoveNext()
1417 if (_version
!= _dictionary
._version
)
1419 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1422 while ((uint)_index
< (uint)_dictionary
._count
)
1424 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1426 if (entry
.next
>= -1)
1428 _currentKey
= entry
.key
;
1433 _index
= _dictionary
._count
+ 1;
1434 _currentKey
= default;
1438 public TKey Current
=> _currentKey
!;
1440 object? IEnumerator
.Current
1444 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1446 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1453 void IEnumerator
.Reset()
1455 if (_version
!= _dictionary
._version
)
1457 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1461 _currentKey
= default;
1466 [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView
<,>))]
1467 [DebuggerDisplay("Count = {Count}")]
1468 public sealed class ValueCollection
: ICollection
<TValue
>, ICollection
, IReadOnlyCollection
<TValue
>
1470 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1472 public ValueCollection(Dictionary
<TKey
, TValue
> dictionary
)
1474 if (dictionary
== null)
1476 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.dictionary
);
1478 _dictionary
= dictionary
;
1481 public Enumerator
GetEnumerator()
1482 => new Enumerator(_dictionary
);
1484 public void CopyTo(TValue
[] array
, int index
)
1488 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1491 if ((uint)index
> array
.Length
)
1493 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1496 if (array
.Length
- index
< _dictionary
.Count
)
1498 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1501 int count
= _dictionary
._count
;
1502 Entry
[]? entries
= _dictionary
._entries
;
1503 for (int i
= 0; i
< count
; i
++)
1505 if (entries
![i
].next
>= -1) array
[index
++] = entries
[i
].value;
1509 public int Count
=> _dictionary
.Count
;
1511 bool ICollection
<TValue
>.IsReadOnly
=> true;
1513 void ICollection
<TValue
>.Add(TValue item
)
1514 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1516 bool ICollection
<TValue
>.Remove(TValue item
)
1518 ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1522 void ICollection
<TValue
>.Clear()
1523 => ThrowHelper
.ThrowNotSupportedException(ExceptionResource
.NotSupported_ValueCollectionSet
);
1525 bool ICollection
<TValue
>.Contains(TValue item
)
1526 => _dictionary
.ContainsValue(item
);
1528 IEnumerator
<TValue
> IEnumerable
<TValue
>.GetEnumerator()
1529 => new Enumerator(_dictionary
);
1531 IEnumerator IEnumerable
.GetEnumerator()
1532 => new Enumerator(_dictionary
);
1534 void ICollection
.CopyTo(Array array
, int index
)
1537 ThrowHelper
.ThrowArgumentNullException(ExceptionArgument
.array
);
1538 if (array
.Rank
!= 1)
1539 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_RankMultiDimNotSupported
);
1540 if (array
.GetLowerBound(0) != 0)
1541 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_NonZeroLowerBound
);
1542 if ((uint)index
> (uint)array
.Length
)
1543 ThrowHelper
.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1544 if (array
.Length
- index
< _dictionary
.Count
)
1545 ThrowHelper
.ThrowArgumentException(ExceptionResource
.Arg_ArrayPlusOffTooSmall
);
1547 if (array
is TValue
[] values
)
1549 CopyTo(values
, index
);
1553 object[]? objects
= array
as object[];
1554 if (objects
== null)
1556 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1559 int count
= _dictionary
._count
;
1560 Entry
[]? entries
= _dictionary
._entries
;
1563 for (int i
= 0; i
< count
; i
++)
1565 if (entries
![i
].next
>= -1) objects
[index
++] = entries
[i
].value!;
1568 catch (ArrayTypeMismatchException
)
1570 ThrowHelper
.ThrowArgumentException_Argument_InvalidArrayType();
1575 bool ICollection
.IsSynchronized
=> false;
1577 object ICollection
.SyncRoot
=> ((ICollection
)_dictionary
).SyncRoot
;
1579 public struct Enumerator
: IEnumerator
<TValue
>, IEnumerator
1581 private readonly Dictionary
<TKey
, TValue
> _dictionary
;
1583 private readonly int _version
;
1584 [AllowNull
, MaybeNull
] private TValue _currentValue
;
1586 internal Enumerator(Dictionary
<TKey
, TValue
> dictionary
)
1588 _dictionary
= dictionary
;
1589 _version
= dictionary
._version
;
1591 _currentValue
= default;
1594 public void Dispose()
1598 public bool MoveNext()
1600 if (_version
!= _dictionary
._version
)
1602 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1605 while ((uint)_index
< (uint)_dictionary
._count
)
1607 ref Entry entry
= ref _dictionary
._entries
![_index
++];
1609 if (entry
.next
>= -1)
1611 _currentValue
= entry
.value;
1615 _index
= _dictionary
._count
+ 1;
1616 _currentValue
= default;
1620 public TValue Current
=> _currentValue
!;
1622 object? IEnumerator
.Current
1626 if (_index
== 0 || (_index
== _dictionary
._count
+ 1))
1628 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1631 return _currentValue
;
1635 void IEnumerator
.Reset()
1637 if (_version
!= _dictionary
._version
)
1639 ThrowHelper
.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1642 _currentValue
= default;