Contribute to IDE0044 (make field readonly)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Collections / Generic / Dictionary.cs
blob1bd9056265cedb5ab69823e0275083afe85d6964
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
12 /// <summary>
13 /// Used internally to control behavior of insertion into a <see cref="Dictionary{TKey, TValue}"/>.
14 /// </summary>
15 internal enum InsertionBehavior : byte
17 /// <summary>
18 /// The default insertion behavior.
19 /// </summary>
20 None = 0,
22 /// <summary>
23 /// Specifies that an existing entry with the same key should be overwritten if encountered.
24 /// </summary>
25 OverwriteExisting = 1,
27 /// <summary>
28 /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
29 /// </summary>
30 ThrowOnExisting = 2
33 [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
34 [DebuggerDisplay("Count = {Count}")]
35 [Serializable]
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
39 private struct Entry
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.
44 public int next;
45 public uint hashCode;
46 public TKey key; // Key of entry
47 public TValue value; // Value of entry
50 private int[]? _buckets;
51 private Entry[]? _entries;
52 private int _count;
53 private int _freeList;
54 private int _freeCount;
55 private int _version;
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)
79 _comparer = comparer;
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);
115 return;
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;
156 public int Count
158 get { return _count - _freeCount; }
161 public KeyCollection Keys
165 if (_keys == null) _keys = new KeyCollection(this);
166 return _keys;
170 ICollection<TKey> IDictionary<TKey, TValue>.Keys
174 if (_keys == null) _keys = new KeyCollection(this);
175 return _keys;
179 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
183 if (_keys == null) _keys = new KeyCollection(this);
184 return _keys;
188 public ValueCollection Values
192 if (_values == null) _values = new ValueCollection(this);
193 return _values;
197 ICollection<TValue> IDictionary<TKey, TValue>.Values
201 if (_values == null) _values = new ValueCollection(this);
202 return _values;
206 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
210 if (_values == null) _values = new ValueCollection(this);
211 return _values;
215 public TValue this[TKey key]
219 int i = FindEntry(key);
220 if (i >= 0) return _entries![i].value;
221 ThrowHelper.ThrowKeyNotFoundException(key);
222 return default;
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))
245 return true;
247 return false;
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);
256 return true;
258 return false;
261 public void Clear()
263 int count = _count;
264 if (count > 0)
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);
271 _count = 0;
272 _freeList = -1;
273 _freeCount = 0;
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;
284 if (value == null)
286 for (int i = 0; i < _count; i++)
288 if (entries![i].next >= -1 && entries[i].value == null) return true;
291 else
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;
301 else
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;
313 return false;
316 private void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
318 if (array == null)
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);
333 int count = _count;
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)
352 if (info == null)
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];
364 CopyTo(array, 0);
365 info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[]));
369 private int FindEntry(TKey key)
371 if (key == null)
373 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
376 int i = -1;
377 int[]? buckets = _buckets;
378 Entry[]? entries = _entries;
379 int collisionCount = 0;
380 if (buckets != null)
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)))
398 break;
401 i = entries[i].next;
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();
408 collisionCount++;
409 } while (true);
411 else
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)))
423 break;
426 i = entries[i].next;
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();
433 collisionCount++;
434 } while (true);
437 else
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)))
449 break;
452 i = entries[i].next;
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();
459 collisionCount++;
460 } while (true);
464 return i;
467 private int Initialize(int capacity)
469 int size = HashHelpers.GetPrime(capacity);
471 _freeList = -1;
472 _buckets = new int[size];
473 _entries = new Entry[size];
475 return size;
478 private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
480 if (key == null)
482 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
485 if (_buckets == null)
487 Initialize(0);
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
500 int i = bucket - 1;
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)
513 break;
516 if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
518 if (behavior == InsertionBehavior.OverwriteExisting)
520 entries[i].value = value;
521 _version++;
522 return true;
525 if (behavior == InsertionBehavior.ThrowOnExisting)
527 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
530 return false;
533 i = entries[i].next;
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();
540 collisionCount++;
541 } while (true);
543 else
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)
555 break;
558 if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
560 if (behavior == InsertionBehavior.OverwriteExisting)
562 entries[i].value = value;
563 _version++;
564 return true;
567 if (behavior == InsertionBehavior.ThrowOnExisting)
569 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
572 return false;
575 i = entries[i].next;
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();
582 collisionCount++;
583 } while (true);
586 else
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)
594 break;
597 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
599 if (behavior == InsertionBehavior.OverwriteExisting)
601 entries[i].value = value;
602 _version++;
603 return true;
606 if (behavior == InsertionBehavior.ThrowOnExisting)
608 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
611 return false;
614 i = entries[i].next;
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();
621 collisionCount++;
622 } while (true);
626 bool updateFreeList = false;
627 int index;
628 if (_freeCount > 0)
630 index = _freeList;
631 updateFreeList = true;
632 _freeCount--;
634 else
636 int count = _count;
637 if (count == entries.Length)
639 Resize();
640 bucket = ref _buckets[hashCode % (uint)_buckets.Length];
642 index = count;
643 _count = count + 1;
644 entries = _entries;
647 ref Entry entry = ref entries![index];
649 if (updateFreeList)
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;
658 entry.key = key;
659 entry.value = value;
660 // Value in _buckets is 1-based
661 bucket = index + 1;
662 _version++;
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.
669 _comparer = null;
670 Resize(entries.Length, true);
673 return true;
676 public virtual void OnDeserialization(object? sender)
678 HashHelpers.SerializationInfoTable.TryGetValue(this, out SerializationInfo? siInfo);
680 if (siInfo == null)
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.
684 return;
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.
691 if (hashsize != 0)
693 Initialize(hashsize);
695 KeyValuePair<TKey, TValue>[]? array = (KeyValuePair<TKey, TValue>[]?)
696 siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[]));
698 if (array == null)
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);
712 else
714 _buckets = null;
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];
734 int count = _count;
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;
761 _buckets = buckets;
762 _entries = entries;
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)
770 if (key == null)
772 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
775 int[]? buckets = _buckets;
776 Entry[]? entries = _entries;
777 int collisionCount = 0;
778 if (buckets != null)
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;
783 int last = -1;
784 // Value in buckets is 1-based
785 int i = buckets[bucket] - 1;
786 while (i >= 0)
788 ref Entry entry = ref entries[i];
790 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
792 if (last < 0)
794 // Value in buckets is 1-based
795 buckets[bucket] = entry.next + 1;
797 else
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!;
814 _freeList = i;
815 _freeCount++;
816 return true;
819 last = i;
820 i = entry.next;
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();
827 collisionCount++;
830 return false;
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)
838 if (key == null)
840 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
843 int[]? buckets = _buckets;
844 Entry[]? entries = _entries;
845 int collisionCount = 0;
846 if (buckets != null)
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;
851 int last = -1;
852 // Value in buckets is 1-based
853 int i = buckets[bucket] - 1;
854 while (i >= 0)
856 ref Entry entry = ref entries[i];
858 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
860 if (last < 0)
862 // Value in buckets is 1-based
863 buckets[bucket] = entry.next + 1;
865 else
867 entries[last].next = entry.next;
870 value = entry.value;
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!;
884 _freeList = i;
885 _freeCount++;
886 return true;
889 last = i;
890 i = entry.next;
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();
897 collisionCount++;
900 value = default!;
901 return false;
904 public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
906 int i = FindEntry(key);
907 if (i >= 0)
909 value = _entries![i].value;
910 return true;
912 value = default!;
913 return false;
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)
926 if (array == null)
927 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
928 if (array.Rank != 1)
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);
952 else
954 object[]? objects = array as object[];
955 if (objects == null)
957 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
962 int count = _count;
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);
982 /// <summary>
983 /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
984 /// </summary>
985 public int EnsureCapacity(int capacity)
987 if (capacity < 0)
988 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
989 int currentCapacity = _entries == null ? 0 : _entries.Length;
990 if (currentCapacity >= capacity)
991 return currentCapacity;
992 _version++;
993 if (_buckets == null)
994 return Initialize(capacity);
995 int newSize = HashHelpers.GetPrime(capacity);
996 Resize(newSize, forceNewHashCodes: false);
997 return newSize;
1000 /// <summary>
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();
1010 /// </summary>
1011 public void TrimExcess()
1012 => TrimExcess(Count);
1014 /// <summary>
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.
1019 /// </summary>
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)
1029 return;
1031 int oldCount = _count;
1032 _version++;
1033 Initialize(newSize);
1034 Entry[]? entries = _entries;
1035 int[]? buckets = _buckets;
1036 int count = 0;
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;
1049 count++;
1052 _count = count;
1053 _freeCount = 0;
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);
1075 if (i >= 0)
1077 return _entries![i].value;
1080 return null;
1084 if (key == null)
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)
1111 if (key == null)
1113 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
1115 return (key is TKey);
1118 void IDictionary.Add(object key, object? value)
1120 if (key == null)
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);
1152 return false;
1155 IDictionaryEnumerator IDictionary.GetEnumerator()
1156 => new Enumerator(this, Enumerator.DictEntry);
1158 void IDictionary.Remove(object key)
1160 if (IsCompatibleKey(key))
1162 Remove((TKey)key);
1166 public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>,
1167 IDictionaryEnumerator
1169 private readonly Dictionary<TKey, TValue> _dictionary;
1170 private readonly int _version;
1171 private int _index;
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;
1182 _index = 0;
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);
1203 return true;
1207 _index = _dictionary._count + 1;
1208 _current = new KeyValuePair<TKey, TValue>();
1209 return false;
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);
1231 else
1233 return new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
1238 void IEnumerator.Reset()
1240 if (_version != _dictionary._version)
1242 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1245 _index = 0;
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)
1309 if (array == null)
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);
1348 return false;
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)
1359 if (array == null)
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);
1374 else
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;
1405 private int _index;
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;
1413 _index = 0;
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;
1435 return true;
1439 _index = _dictionary._count + 1;
1440 _currentKey = default;
1441 return false;
1444 public TKey Current => _currentKey!;
1446 object? IEnumerator.Current
1450 if (_index == 0 || (_index == _dictionary._count + 1))
1452 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1455 return _currentKey;
1459 void IEnumerator.Reset()
1461 if (_version != _dictionary._version)
1463 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1466 _index = 0;
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)
1492 if (array == null)
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);
1525 return false;
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)
1542 if (array == null)
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);
1557 else
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;
1588 private int _index;
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;
1596 _index = 0;
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;
1618 return true;
1621 _index = _dictionary._count + 1;
1622 _currentValue = default;
1623 return false;
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();
1647 _index = 0;
1648 _currentValue = default;