Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Collections / Generic / Dictionary.cs
blob0b2fc45c24c066ddd2cfab7ebd7a926065c7133c
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 =>
149 (_comparer == null ||_comparer is NonRandomizedStringEqualityComparer) ?
150 EqualityComparer<TKey>.Default :
151 _comparer;
153 public int Count => _count - _freeCount;
155 public KeyCollection Keys
159 if (_keys == null) _keys = new KeyCollection(this);
160 return _keys;
164 ICollection<TKey> IDictionary<TKey, TValue>.Keys
168 if (_keys == null) _keys = new KeyCollection(this);
169 return _keys;
173 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
177 if (_keys == null) _keys = new KeyCollection(this);
178 return _keys;
182 public ValueCollection Values
186 if (_values == null) _values = new ValueCollection(this);
187 return _values;
191 ICollection<TValue> IDictionary<TKey, TValue>.Values
195 if (_values == null) _values = new ValueCollection(this);
196 return _values;
200 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
204 if (_values == null) _values = new ValueCollection(this);
205 return _values;
209 public TValue this[TKey key]
213 int i = FindEntry(key);
214 if (i >= 0) return _entries![i].value;
215 ThrowHelper.ThrowKeyNotFoundException(key);
216 return default;
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))
239 return true;
241 return false;
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);
250 return true;
252 return false;
255 public void Clear()
257 int count = _count;
258 if (count > 0)
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);
265 _count = 0;
266 _freeList = -1;
267 _freeCount = 0;
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;
278 if (value == null)
280 for (int i = 0; i < _count; i++)
282 if (entries![i].next >= -1 && entries[i].value == null) return true;
285 else
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;
295 else
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;
307 return false;
310 private void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
312 if (array == null)
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);
327 int count = _count;
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)
346 if (info == null)
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];
358 CopyTo(array, 0);
359 info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[]));
363 private int FindEntry(TKey key)
365 if (key == null)
367 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
370 int i = -1;
371 int[]? buckets = _buckets;
372 Entry[]? entries = _entries;
373 int collisionCount = 0;
374 if (buckets != null)
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)))
392 break;
395 i = entries[i].next;
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();
402 collisionCount++;
403 } while (true);
405 else
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)))
417 break;
420 i = entries[i].next;
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();
427 collisionCount++;
428 } while (true);
431 else
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)))
443 break;
446 i = entries[i].next;
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();
453 collisionCount++;
454 } while (true);
458 return i;
461 private int Initialize(int capacity)
463 int size = HashHelpers.GetPrime(capacity);
465 _freeList = -1;
466 _buckets = new int[size];
467 _entries = new Entry[size];
469 return size;
472 private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
474 if (key == null)
476 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
479 if (_buckets == null)
481 Initialize(0);
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
494 int i = bucket - 1;
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)
507 break;
510 if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
512 if (behavior == InsertionBehavior.OverwriteExisting)
514 entries[i].value = value;
515 _version++;
516 return true;
519 if (behavior == InsertionBehavior.ThrowOnExisting)
521 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
524 return false;
527 i = entries[i].next;
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();
534 collisionCount++;
535 } while (true);
537 else
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)
549 break;
552 if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
554 if (behavior == InsertionBehavior.OverwriteExisting)
556 entries[i].value = value;
557 _version++;
558 return true;
561 if (behavior == InsertionBehavior.ThrowOnExisting)
563 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
566 return false;
569 i = entries[i].next;
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();
576 collisionCount++;
577 } while (true);
580 else
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)
588 break;
591 if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
593 if (behavior == InsertionBehavior.OverwriteExisting)
595 entries[i].value = value;
596 _version++;
597 return true;
600 if (behavior == InsertionBehavior.ThrowOnExisting)
602 ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
605 return false;
608 i = entries[i].next;
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();
615 collisionCount++;
616 } while (true);
620 bool updateFreeList = false;
621 int index;
622 if (_freeCount > 0)
624 index = _freeList;
625 updateFreeList = true;
626 _freeCount--;
628 else
630 int count = _count;
631 if (count == entries.Length)
633 Resize();
634 bucket = ref _buckets[hashCode % (uint)_buckets.Length];
636 index = count;
637 _count = count + 1;
638 entries = _entries;
641 ref Entry entry = ref entries![index];
643 if (updateFreeList)
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;
652 entry.key = key;
653 entry.value = value;
654 // Value in _buckets is 1-based
655 bucket = index + 1;
656 _version++;
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.
663 _comparer = null;
664 Resize(entries.Length, true);
667 return true;
670 public virtual void OnDeserialization(object? sender)
672 HashHelpers.SerializationInfoTable.TryGetValue(this, out SerializationInfo? siInfo);
674 if (siInfo == null)
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.
678 return;
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.
685 if (hashsize != 0)
687 Initialize(hashsize);
689 KeyValuePair<TKey, TValue>[]? array = (KeyValuePair<TKey, TValue>[]?)
690 siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[]));
692 if (array == null)
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);
706 else
708 _buckets = null;
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];
728 int count = _count;
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;
755 _buckets = buckets;
756 _entries = entries;
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)
764 if (key == null)
766 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
769 int[]? buckets = _buckets;
770 Entry[]? entries = _entries;
771 int collisionCount = 0;
772 if (buckets != null)
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;
777 int last = -1;
778 // Value in buckets is 1-based
779 int i = buckets[bucket] - 1;
780 while (i >= 0)
782 ref Entry entry = ref entries[i];
784 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
786 if (last < 0)
788 // Value in buckets is 1-based
789 buckets[bucket] = entry.next + 1;
791 else
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!;
808 _freeList = i;
809 _freeCount++;
810 return true;
813 last = i;
814 i = entry.next;
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();
821 collisionCount++;
824 return false;
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)
832 if (key == null)
834 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
837 int[]? buckets = _buckets;
838 Entry[]? entries = _entries;
839 int collisionCount = 0;
840 if (buckets != null)
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;
845 int last = -1;
846 // Value in buckets is 1-based
847 int i = buckets[bucket] - 1;
848 while (i >= 0)
850 ref Entry entry = ref entries[i];
852 if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
854 if (last < 0)
856 // Value in buckets is 1-based
857 buckets[bucket] = entry.next + 1;
859 else
861 entries[last].next = entry.next;
864 value = entry.value;
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!;
878 _freeList = i;
879 _freeCount++;
880 return true;
883 last = i;
884 i = entry.next;
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();
891 collisionCount++;
894 value = default!;
895 return false;
898 public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
900 int i = FindEntry(key);
901 if (i >= 0)
903 value = _entries![i].value;
904 return true;
906 value = default!;
907 return false;
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)
920 if (array == null)
921 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
922 if (array.Rank != 1)
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);
946 else
948 object[]? objects = array as object[];
949 if (objects == null)
951 ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
956 int count = _count;
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);
976 /// <summary>
977 /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
978 /// </summary>
979 public int EnsureCapacity(int capacity)
981 if (capacity < 0)
982 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
983 int currentCapacity = _entries == null ? 0 : _entries.Length;
984 if (currentCapacity >= capacity)
985 return currentCapacity;
986 _version++;
987 if (_buckets == null)
988 return Initialize(capacity);
989 int newSize = HashHelpers.GetPrime(capacity);
990 Resize(newSize, forceNewHashCodes: false);
991 return newSize;
994 /// <summary>
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();
1004 /// </summary>
1005 public void TrimExcess()
1006 => TrimExcess(Count);
1008 /// <summary>
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.
1013 /// </summary>
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)
1023 return;
1025 int oldCount = _count;
1026 _version++;
1027 Initialize(newSize);
1028 Entry[]? entries = _entries;
1029 int[]? buckets = _buckets;
1030 int count = 0;
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;
1043 count++;
1046 _count = count;
1047 _freeCount = 0;
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);
1069 if (i >= 0)
1071 return _entries![i].value;
1074 return null;
1078 if (key == null)
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)
1105 if (key == null)
1107 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
1109 return (key is TKey);
1112 void IDictionary.Add(object key, object? value)
1114 if (key == null)
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);
1146 return false;
1149 IDictionaryEnumerator IDictionary.GetEnumerator()
1150 => new Enumerator(this, Enumerator.DictEntry);
1152 void IDictionary.Remove(object key)
1154 if (IsCompatibleKey(key))
1156 Remove((TKey)key);
1160 public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>,
1161 IDictionaryEnumerator
1163 private readonly Dictionary<TKey, TValue> _dictionary;
1164 private readonly int _version;
1165 private int _index;
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;
1176 _index = 0;
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);
1197 return true;
1201 _index = _dictionary._count + 1;
1202 _current = new KeyValuePair<TKey, TValue>();
1203 return false;
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);
1225 else
1227 return new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
1232 void IEnumerator.Reset()
1234 if (_version != _dictionary._version)
1236 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1239 _index = 0;
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)
1303 if (array == null)
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);
1342 return false;
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)
1353 if (array == null)
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);
1368 else
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;
1399 private int _index;
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;
1407 _index = 0;
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;
1429 return true;
1433 _index = _dictionary._count + 1;
1434 _currentKey = default;
1435 return false;
1438 public TKey Current => _currentKey!;
1440 object? IEnumerator.Current
1444 if (_index == 0 || (_index == _dictionary._count + 1))
1446 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
1449 return _currentKey;
1453 void IEnumerator.Reset()
1455 if (_version != _dictionary._version)
1457 ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
1460 _index = 0;
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)
1486 if (array == null)
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);
1519 return false;
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)
1536 if (array == null)
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);
1551 else
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;
1582 private int _index;
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;
1590 _index = 0;
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;
1612 return true;
1615 _index = _dictionary._count + 1;
1616 _currentValue = default;
1617 return false;
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();
1641 _index = 0;
1642 _currentValue = default;