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.Runtime.CompilerServices;
7 using System.Runtime.Serialization;
8 using System.Threading;
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
41 public int hashCode; // Lower 31 bits of hash code, -1 if unused
42 public int next; // Index of next entry, -1 if last
43 public TKey key; // Key of entry
44 public TValue value; // Value of entry
47 private int[] _buckets;
48 private Entry[] _entries;
50 private int _freeList;
51 private int _freeCount;
53 private IEqualityComparer<TKey> _comparer;
54 private KeyCollection _keys;
55 private ValueCollection _values;
56 private object _syncRoot;
58 // constants for serialization
59 private const string VersionName = "Version"; // Do not rename (binary serialization)
60 private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
61 private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization)
62 private const string ComparerName = "Comparer"; // Do not rename (binary serialization)
64 public Dictionary() : this(0, null) { }
66 public Dictionary(int capacity) : this(capacity, null) { }
68 public Dictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { }
70 public Dictionary(int capacity, IEqualityComparer<TKey> comparer)
72 if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
73 if (capacity > 0) Initialize(capacity);
74 if (comparer != EqualityComparer<TKey>.Default)
79 if (typeof(TKey) == typeof(string) && _comparer == null)
81 // To start, move off default comparer for string which is randomised
82 _comparer = (IEqualityComparer<TKey>)NonRandomizedStringEqualityComparer.Default;
86 public Dictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null) { }
88 public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) :
89 this(dictionary != null ? dictionary.Count : 0, comparer)
91 if (dictionary == null)
93 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
96 // It is likely that the passed-in dictionary is Dictionary<TKey,TValue>. When this is the case,
97 // avoid the enumerator allocation and overhead by looping through the entries array directly.
98 // We only do this when dictionary is Dictionary<TKey,TValue> and not a subclass, to maintain
99 // back-compat with subclasses that may have overridden the enumerator behavior.
100 if (dictionary.GetType() == typeof(Dictionary<TKey, TValue>))
102 Dictionary<TKey, TValue> d = (Dictionary<TKey, TValue>)dictionary;
103 int count = d._count;
104 Entry[] entries = d._entries;
105 for (int i = 0; i < count; i++)
107 if (entries[i].hashCode >= 0)
109 Add(entries[i].key, entries[i].value);
115 foreach (KeyValuePair<TKey, TValue> pair in dictionary)
117 Add(pair.Key, pair.Value);
121 public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null) { }
123 public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) :
124 this((collection as ICollection<KeyValuePair<TKey, TValue>>)?.Count ?? 0, comparer)
126 if (collection == null)
128 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
131 foreach (KeyValuePair<TKey, TValue> pair in collection)
133 Add(pair.Key, pair.Value);
137 protected Dictionary(SerializationInfo info, StreamingContext context)
139 // We can't do anything with the keys and values until the entire graph has been deserialized
140 // and we have a resonable estimate that GetHashCode is not going to fail. For the time being,
141 // we'll just cache this. The graph is not valid until OnDeserialization has been called.
142 HashHelpers.SerializationInfoTable.Add(this, info);
145 public IEqualityComparer<TKey> Comparer
149 return (_comparer == null || _comparer is NonRandomizedStringEqualityComparer) ? EqualityComparer<TKey>.Default : _comparer;
155 get { return _count - _freeCount; }
158 public KeyCollection Keys
162 if (_keys == null) _keys = new KeyCollection(this);
167 ICollection<TKey> IDictionary<TKey, TValue>.Keys
171 if (_keys == null) _keys = new KeyCollection(this);
176 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
180 if (_keys == null) _keys = new KeyCollection(this);
185 public ValueCollection Values
189 if (_values == null) _values = new ValueCollection(this);
194 ICollection<TValue> IDictionary<TKey, TValue>.Values
198 if (_values == null) _values = new ValueCollection(this);
203 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
207 if (_values == null) _values = new ValueCollection(this);
212 public TValue this[TKey key]
216 int i = FindEntry(key);
217 if (i >= 0) return _entries[i].value;
218 ThrowHelper.ThrowKeyNotFoundException(key);
223 bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
224 Debug.Assert(modified);
228 public void Add(TKey key, TValue value)
230 bool modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
231 Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown.
234 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair)
235 => Add(keyValuePair.Key, keyValuePair.Value);
237 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
239 int i = FindEntry(keyValuePair.Key);
240 if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
247 bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
249 int i = FindEntry(keyValuePair.Key);
250 if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
252 Remove(keyValuePair.Key);
263 Array.Clear(_buckets, 0, _buckets.Length);
268 Array.Clear(_entries, 0, count);
273 public bool ContainsKey(TKey key)
274 => FindEntry(key) >= 0;
276 public bool ContainsValue(TValue value)
280 for (int i = 0; i < _count; i++)
282 if (_entries[i].hashCode >= 0 && _entries[i].value == null) return true;
287 for (int i = 0; i < _count; i++)
289 if (_entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, value)) return true;
295 private void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
299 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
302 if ((uint)index > (uint)array.Length)
304 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
307 if (array.Length - index < Count)
309 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
313 Entry[] entries = _entries;
314 for (int i = 0; i < count; i++)
316 if (entries[i].hashCode >= 0)
318 array[index + i] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
323 public Enumerator GetEnumerator()
324 => new Enumerator(this, Enumerator.KeyValuePair);
326 IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
327 => new Enumerator(this, Enumerator.KeyValuePair);
329 public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
333 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info);
336 info.AddValue(VersionName, _version);
337 info.AddValue(ComparerName, _comparer ?? EqualityComparer<TKey>.Default, typeof(IEqualityComparer<TKey>));
338 info.AddValue(HashSizeName, _buckets == null ? 0 : _buckets.Length); // This is the length of the bucket array
340 if (_buckets != null)
342 var array = new KeyValuePair<TKey, TValue>[Count];
344 info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[]));
348 private int FindEntry(TKey key)
352 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
356 int[] buckets = _buckets;
357 Entry[] entries = _entries;
358 int collisionCount = 0;
361 IEqualityComparer<TKey> comparer = _comparer;
362 if (comparer == null)
364 int hashCode = key.GetHashCode() & 0x7FFFFFFF;
365 // Value in _buckets is 1-based
366 i = buckets[hashCode % buckets.Length] - 1;
369 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
370 // Test in if to drop range check for following array access
371 if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
377 if (collisionCount >= entries.Length)
379 // The chain of entries forms a loop; which means a concurrent update has happened.
380 // Break out of the loop and throw, rather than looping forever.
381 ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
388 int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
389 // Value in _buckets is 1-based
390 i = buckets[hashCode % buckets.Length] - 1;
393 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
394 // Test in if to drop range check for following array access
395 if ((uint)i >= (uint)entries.Length ||
396 (entries[i].hashCode == hashCode && comparer.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();
416 private int Initialize(int capacity)
418 int size = HashHelpers.GetPrime(capacity);
421 _buckets = new int[size];
422 _entries = new Entry[size];
427 private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
431 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
435 if (_buckets == null)
440 Entry[] entries = _entries;
441 IEqualityComparer<TKey> comparer = _comparer;
443 int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF;
445 int collisionCount = 0;
446 ref int bucket = ref _buckets[hashCode % _buckets.Length];
447 // Value in _buckets is 1-based
450 if (comparer == null)
454 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
455 // Test uint in if rather than loop condition to drop range check for following array access
456 if ((uint)i >= (uint)entries.Length)
461 if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
463 if (behavior == InsertionBehavior.OverwriteExisting)
465 entries[i].value = value;
469 if (behavior == InsertionBehavior.ThrowOnExisting)
471 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
478 if (collisionCount >= entries.Length)
480 // The chain of entries forms a loop; which means a concurrent update has happened.
481 // Break out of the loop and throw, rather than looping forever.
482 ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
491 // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
492 // Test uint in if rather than loop condition to drop range check for following array access
493 if ((uint)i >= (uint)entries.Length)
498 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
500 if (behavior == InsertionBehavior.OverwriteExisting)
502 entries[i].value = value;
506 if (behavior == InsertionBehavior.ThrowOnExisting)
508 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
515 if (collisionCount >= entries.Length)
517 // The chain of entries forms a loop; which means a concurrent update has happened.
518 // Break out of the loop and throw, rather than looping forever.
519 ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
526 // Can be improved with "Ref Local Reassignment"
527 // https://github.com/dotnet/csharplang/blob/master/proposals/ref-local-reassignment.md
528 bool resized = false;
529 bool updateFreeList = false;
534 updateFreeList = true;
540 if (count == entries.Length)
550 ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket;
551 ref Entry entry = ref entries[index];
555 _freeList = entry.next;
557 entry.hashCode = hashCode;
558 // Value in _buckets is 1-based
559 entry.next = targetBucket - 1;
562 // Value in _buckets is 1-based
563 targetBucket = index + 1;
565 // Value types never rehash
566 if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer)
568 // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
569 // i.e. EqualityComparer<string>.Default.
571 Resize(entries.Length, true);
577 public virtual void OnDeserialization(object sender)
579 HashHelpers.SerializationInfoTable.TryGetValue(this, out SerializationInfo siInfo);
583 // We can return immediately if this function is called twice.
584 // Note we remove the serialization info from the table at the end of this method.
588 int realVersion = siInfo.GetInt32(VersionName);
589 int hashsize = siInfo.GetInt32(HashSizeName);
590 _comparer = (IEqualityComparer<TKey>)siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>));
594 Initialize(hashsize);
596 KeyValuePair<TKey, TValue>[] array = (KeyValuePair<TKey, TValue>[])
597 siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[]));
601 ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingKeys);
604 for (int i = 0; i < array.Length; i++)
606 if (array[i].Key == null)
608 ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_NullKey);
610 Add(array[i].Key, array[i].Value);
618 _version = realVersion;
619 HashHelpers.SerializationInfoTable.Remove(this);
622 private void Resize()
623 => Resize(HashHelpers.ExpandPrime(_count), false);
625 private void Resize(int newSize, bool forceNewHashCodes)
627 // Value types never rehash
628 Debug.Assert(!forceNewHashCodes || default(TKey) == null);
629 Debug.Assert(newSize >= _entries.Length);
631 int[] buckets = new int[newSize];
632 Entry[] entries = new Entry[newSize];
635 Array.Copy(_entries, 0, entries, 0, count);
637 if (default(TKey) == null && forceNewHashCodes)
639 for (int i = 0; i < count; i++)
641 if (entries[i].hashCode >= 0)
643 Debug.Assert(_comparer == null);
644 entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF);
649 for (int i = 0; i < count; i++)
651 if (entries[i].hashCode >= 0)
653 int bucket = entries[i].hashCode % newSize;
654 // Value in _buckets is 1-based
655 entries[i].next = buckets[bucket] - 1;
656 // Value in _buckets is 1-based
657 buckets[bucket] = i + 1;
665 // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
666 // statement to copy the value for entry being removed into the output parameter.
667 // Code has been intentionally duplicated for performance reasons.
668 public bool Remove(TKey key)
672 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
675 if (_buckets != null)
677 int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
678 int bucket = hashCode % _buckets.Length;
680 // Value in _buckets is 1-based
681 int i = _buckets[bucket] - 1;
684 ref Entry entry = ref _entries[i];
686 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
690 // Value in _buckets is 1-based
691 _buckets[bucket] = entry.next + 1;
695 _entries[last].next = entry.next;
698 entry.next = _freeList;
700 if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
704 if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
706 entry.value = default;
721 // This overload is a copy of the overload Remove(TKey key) with one additional
722 // statement to copy the value for entry being removed into the output parameter.
723 // Code has been intentionally duplicated for performance reasons.
724 public bool Remove(TKey key, out TValue value)
728 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
731 if (_buckets != null)
733 int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
734 int bucket = hashCode % _buckets.Length;
736 // Value in _buckets is 1-based
737 int i = _buckets[bucket] - 1;
740 ref Entry entry = ref _entries[i];
742 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
746 // Value in _buckets is 1-based
747 _buckets[bucket] = entry.next + 1;
751 _entries[last].next = entry.next;
757 entry.next = _freeList;
759 if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
763 if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
765 entry.value = default;
781 public bool TryGetValue(TKey key, out TValue value)
783 int i = FindEntry(key);
786 value = _entries[i].value;
793 public bool TryAdd(TKey key, TValue value)
794 => TryInsert(key, value, InsertionBehavior.None);
796 bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
798 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
799 => CopyTo(array, index);
801 void ICollection.CopyTo(Array array, int index)
804 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
806 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
807 if (array.GetLowerBound(0) != 0)
808 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
809 if ((uint)index > (uint)array.Length)
810 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
811 if (array.Length - index < Count)
812 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
814 if (array is KeyValuePair<TKey, TValue>[] pairs)
816 CopyTo(pairs, index);
818 else if (array is DictionaryEntry[] dictEntryArray)
820 Entry[] entries = _entries;
821 for (int i = 0; i < _count; i++)
823 if (entries[i].hashCode >= 0)
825 dictEntryArray[index + i] = new DictionaryEntry(entries[i].key, entries[i].value);
831 object[] objects = array as object[];
834 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
840 Entry[] entries = _entries;
841 for (int i = 0; i < count; i++)
843 if (entries[i].hashCode >= 0)
845 objects[index + i] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
849 catch (ArrayTypeMismatchException)
851 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
856 IEnumerator IEnumerable.GetEnumerator()
857 => new Enumerator(this, Enumerator.KeyValuePair);
860 /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
862 public int EnsureCapacity(int capacity)
865 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
866 int currentCapacity = _entries == null ? 0 : _entries.Length;
867 if (currentCapacity >= capacity)
868 return currentCapacity;
869 if (_buckets == null)
870 return Initialize(capacity);
871 int newSize = HashHelpers.GetPrime(capacity);
872 Resize(newSize, forceNewHashCodes: false);
877 /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
879 /// This method can be used to minimize the memory overhead
880 /// once it is known that no new elements will be added.
882 /// To allocate minimum size storage array, execute the following statements:
884 /// dictionary.Clear();
885 /// dictionary.TrimExcess();
887 public void TrimExcess()
888 => TrimExcess(Count);
891 /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
893 /// This method can be used to minimize the memory overhead
894 /// once it is known that no new elements will be added.
896 public void TrimExcess(int capacity)
898 if (capacity < Count)
899 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
900 int newSize = HashHelpers.GetPrime(capacity);
902 Entry[] oldEntries = _entries;
903 int currentCapacity = oldEntries == null ? 0 : oldEntries.Length;
904 if (newSize >= currentCapacity)
907 int oldCount = _count;
909 Entry[] entries = _entries;
910 int[] buckets = _buckets;
912 for (int i = 0; i < oldCount; i++)
914 int hashCode = oldEntries[i].hashCode;
917 ref Entry entry = ref entries[count];
918 entry = oldEntries[i];
919 int bucket = hashCode % newSize;
920 // Value in _buckets is 1-based
921 entry.next = buckets[bucket] - 1;
922 // Value in _buckets is 1-based
923 buckets[bucket] = count + 1;
931 bool ICollection.IsSynchronized => false;
933 object ICollection.SyncRoot
937 if (_syncRoot == null)
939 Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
945 bool IDictionary.IsFixedSize => false;
947 bool IDictionary.IsReadOnly => false;
949 ICollection IDictionary.Keys => (ICollection)Keys;
951 ICollection IDictionary.Values => (ICollection)Values;
953 object IDictionary.this[object key]
957 if (IsCompatibleKey(key))
959 int i = FindEntry((TKey)key);
962 return _entries[i].value;
971 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
973 ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
977 TKey tempKey = (TKey)key;
980 this[tempKey] = (TValue)value;
982 catch (InvalidCastException)
984 ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
987 catch (InvalidCastException)
989 ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
994 private static bool IsCompatibleKey(object key)
998 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
1000 return (key is TKey);
1003 void IDictionary.Add(object key, object value)
1007 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
1009 ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
1013 TKey tempKey = (TKey)key;
1017 Add(tempKey, (TValue)value);
1019 catch (InvalidCastException)
1021 ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
1024 catch (InvalidCastException)
1026 ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
1030 bool IDictionary.Contains(object key)
1032 if (IsCompatibleKey(key))
1034 return ContainsKey((TKey)key);
1040 IDictionaryEnumerator IDictionary.GetEnumerator()
1041 => new Enumerator(this, Enumerator.DictEntry);
1043 void IDictionary.Remove(object key)
1045 if (IsCompatibleKey(key))
1051 public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>,
1052 IDictionaryEnumerator
1054 private Dictionary<TKey, TValue> _dictionary;
1055 private int _version;
1057 private KeyValuePair<TKey, TValue> _current;
1058 private int _getEnumeratorRetType; // What should Enumerator.Current return?
1060 internal const int DictEntry = 1;
1061 internal const int KeyValuePair = 2;
1063 internal Enumerator(Dictionary<TKey, TValue> dictionary, int getEnumeratorRetType)
1065 _dictionary = dictionary;
1066 _version = dictionary._version;
1068 _getEnumeratorRetType = getEnumeratorRetType;
1069 _current = new KeyValuePair<TKey, TValue>();
1072 public bool MoveNext()
1074 if (_version != _dictionary._version)
1076 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1079 // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
1080 // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
1081 while ((uint)_index < (uint)_dictionary._count)
1083 ref Entry entry = ref _dictionary._entries[_index++];
1085 if (entry.hashCode >= 0)
1087 _current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
1092 _index = _dictionary._count + 1;
1093 _current = new KeyValuePair<TKey, TValue>();
1097 public KeyValuePair<TKey, TValue> Current => _current;
1099 public void Dispose()
1103 object IEnumerator.Current
1107 if (_index == 0 || (_index == _dictionary._count + 1))
1109 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1112 if (_getEnumeratorRetType == DictEntry)
1114 return new DictionaryEntry(_current.Key, _current.Value);
1118 return new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
1123 void IEnumerator.Reset()
1125 if (_version != _dictionary._version)
1127 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1131 _current = new KeyValuePair<TKey, TValue>();
1134 DictionaryEntry IDictionaryEnumerator.Entry
1138 if (_index == 0 || (_index == _dictionary._count + 1))
1140 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1143 return new DictionaryEntry(_current.Key, _current.Value);
1147 object IDictionaryEnumerator.Key
1151 if (_index == 0 || (_index == _dictionary._count + 1))
1153 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1156 return _current.Key;
1160 object IDictionaryEnumerator.Value
1164 if (_index == 0 || (_index == _dictionary._count + 1))
1166 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1169 return _current.Value;
1174 [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))]
1175 [DebuggerDisplay("Count = {Count}")]
1176 public sealed class KeyCollection : ICollection<TKey>, ICollection, IReadOnlyCollection<TKey>
1178 private Dictionary<TKey, TValue> _dictionary;
1180 public KeyCollection(Dictionary<TKey, TValue> dictionary)
1182 if (dictionary == null)
1184 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
1186 _dictionary = dictionary;
1189 public Enumerator GetEnumerator()
1190 => new Enumerator(_dictionary);
1192 public void CopyTo(TKey[] array, int index)
1196 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
1199 if (index < 0 || index > array.Length)
1201 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1204 if (array.Length - index < _dictionary.Count)
1206 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
1209 int count = _dictionary._count;
1210 Entry[] entries = _dictionary._entries;
1211 for (int i = 0; i < count; i++)
1213 if (entries[i].hashCode >= 0) array[index + i] = entries[i].key;
1217 public int Count => _dictionary.Count;
1219 bool ICollection<TKey>.IsReadOnly => true;
1221 void ICollection<TKey>.Add(TKey item)
1222 => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
1224 void ICollection<TKey>.Clear()
1225 => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
1227 bool ICollection<TKey>.Contains(TKey item)
1228 => _dictionary.ContainsKey(item);
1230 bool ICollection<TKey>.Remove(TKey item)
1232 ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
1236 IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator()
1237 => new Enumerator(_dictionary);
1239 IEnumerator IEnumerable.GetEnumerator()
1240 => new Enumerator(_dictionary);
1242 void ICollection.CopyTo(Array array, int index)
1245 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
1246 if (array.Rank != 1)
1247 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
1248 if (array.GetLowerBound(0) != 0)
1249 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
1250 if ((uint)index > (uint)array.Length)
1251 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1252 if (array.Length - index < _dictionary.Count)
1253 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
1255 if (array is TKey[] keys)
1257 CopyTo(keys, index);
1261 object[] objects = array as object[];
1262 if (objects == null)
1264 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
1267 int count = _dictionary._count;
1268 Entry[] entries = _dictionary._entries;
1271 for (int i = 0; i < count; i++)
1273 if (entries[i].hashCode >= 0) objects[index + i] = entries[i].key;
1276 catch (ArrayTypeMismatchException)
1278 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
1283 bool ICollection.IsSynchronized => false;
1285 object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
1287 public struct Enumerator : IEnumerator<TKey>, IEnumerator
1289 private Dictionary<TKey, TValue> _dictionary;
1291 private int _version;
1292 private TKey _currentKey;
1294 internal Enumerator(Dictionary<TKey, TValue> dictionary)
1296 _dictionary = dictionary;
1297 _version = dictionary._version;
1299 _currentKey = default;
1302 public void Dispose()
1306 public bool MoveNext()
1308 if (_version != _dictionary._version)
1310 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1313 while ((uint)_index < (uint)_dictionary._count)
1315 ref Entry entry = ref _dictionary._entries[_index++];
1317 if (entry.hashCode >= 0)
1319 _currentKey = entry.key;
1324 _index = _dictionary._count + 1;
1325 _currentKey = default;
1329 public TKey Current => _currentKey;
1331 object IEnumerator.Current
1335 if (_index == 0 || (_index == _dictionary._count + 1))
1337 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1344 void IEnumerator.Reset()
1346 if (_version != _dictionary._version)
1348 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1352 _currentKey = default;
1357 [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))]
1358 [DebuggerDisplay("Count = {Count}")]
1359 public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue>
1361 private Dictionary<TKey, TValue> _dictionary;
1363 public ValueCollection(Dictionary<TKey, TValue> dictionary)
1365 if (dictionary == null)
1367 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
1369 _dictionary = dictionary;
1372 public Enumerator GetEnumerator()
1373 => new Enumerator(_dictionary);
1375 public void CopyTo(TValue[] array, int index)
1379 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
1382 if (index < 0 || index > array.Length)
1384 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1387 if (array.Length - index < _dictionary.Count)
1389 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
1392 int count = _dictionary._count;
1393 Entry[] entries = _dictionary._entries;
1394 for (int i = 0; i < count; i++)
1396 if (entries[i].hashCode >= 0) array[index + i] = entries[i].value;
1400 public int Count => _dictionary.Count;
1402 bool ICollection<TValue>.IsReadOnly => true;
1404 void ICollection<TValue>.Add(TValue item)
1405 => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
1407 bool ICollection<TValue>.Remove(TValue item)
1409 ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
1413 void ICollection<TValue>.Clear()
1414 => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
1416 bool ICollection<TValue>.Contains(TValue item)
1417 => _dictionary.ContainsValue(item);
1419 IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
1420 => new Enumerator(_dictionary);
1422 IEnumerator IEnumerable.GetEnumerator()
1423 => new Enumerator(_dictionary);
1425 void ICollection.CopyTo(Array array, int index)
1428 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
1429 if (array.Rank != 1)
1430 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
1431 if (array.GetLowerBound(0) != 0)
1432 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
1433 if ((uint)index > (uint)array.Length)
1434 ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
1435 if (array.Length - index < _dictionary.Count)
1436 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
1438 if (array is TValue[] values)
1440 CopyTo(values, index);
1444 object[] objects = array as object[];
1445 if (objects == null)
1447 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
1450 int count = _dictionary._count;
1451 Entry[] entries = _dictionary._entries;
1454 for (int i = 0; i < count; i++)
1456 if (entries[i].hashCode >= 0) objects[index + i] = entries[i].value;
1459 catch (ArrayTypeMismatchException)
1461 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
1466 bool ICollection.IsSynchronized => false;
1468 object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
1470 public struct Enumerator : IEnumerator<TValue>, IEnumerator
1472 private Dictionary<TKey, TValue> _dictionary;
1474 private int _version;
1475 private TValue _currentValue;
1477 internal Enumerator(Dictionary<TKey, TValue> dictionary)
1479 _dictionary = dictionary;
1480 _version = dictionary._version;
1482 _currentValue = default;
1485 public void Dispose()
1489 public bool MoveNext()
1491 if (_version != _dictionary._version)
1493 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1496 while ((uint)_index < (uint)_dictionary._count)
1498 ref Entry entry = ref _dictionary._entries[_index++];
1500 if (entry.hashCode >= 0)
1502 _currentValue = entry.value;
1506 _index = _dictionary._count + 1;
1507 _currentValue = default;
1511 public TValue Current => _currentValue;
1513 object IEnumerator.Current
1517 if (_index == 0 || (_index == _dictionary._count + 1))
1519 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1522 return _currentValue;
1526 void IEnumerator.Reset()
1528 if (_version != _dictionary._version)
1530 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1533 _currentValue = default;