|
@@ -0,0 +1,1223 @@
|
|
|
|
|
+// Licensed to the .NET Foundation under one or more agreements.
|
|
|
|
|
+// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
|
+// See the LICENSE file in the project root for more information.
|
|
|
|
|
+
|
|
|
|
|
+using System.Diagnostics;
|
|
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
|
|
+using System.Runtime.Serialization;
|
|
|
|
|
+using System.Threading;
|
|
|
|
|
+
|
|
|
|
|
+namespace System.Collections.Generic
|
|
|
|
|
+{
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Used internally to control behavior of insertion into a <see cref="Dictionary{TKey, TValue}"/>.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ internal enum InsertionBehavior : byte
|
|
|
|
|
+ {
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// The default insertion behavior.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ None = 0,
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Specifies that an existing entry with the same key should be overwritten if encountered.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ OverwriteExisting = 1,
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ ThrowOnExisting = 2
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
|
|
|
|
|
+ [DebuggerDisplay("Count = {Count}")]
|
|
|
|
|
+ [Serializable]
|
|
|
|
|
+ [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
|
|
|
|
|
+ public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
|
|
|
|
|
+ {
|
|
|
|
|
+ private struct Entry
|
|
|
|
|
+ {
|
|
|
|
|
+ public int hashCode; // Lower 31 bits of hash code, -1 if unused
|
|
|
|
|
+ public int next; // Index of next entry, -1 if last
|
|
|
|
|
+ public TKey key; // Key of entry
|
|
|
|
|
+ public TValue value; // Value of entry
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int[] _buckets;
|
|
|
|
|
+ private Entry[] _entries;
|
|
|
|
|
+ private int _count;
|
|
|
|
|
+ private int _freeList;
|
|
|
|
|
+ private int _freeCount;
|
|
|
|
|
+ private int _version;
|
|
|
|
|
+ private IEqualityComparer<TKey> _comparer;
|
|
|
|
|
+ private KeyCollection _keys;
|
|
|
|
|
+ private ValueCollection _values;
|
|
|
|
|
+ private object _syncRoot;
|
|
|
|
|
+
|
|
|
|
|
+ // constants for serialization
|
|
|
|
|
+ private const string VersionName = "Version"; // Do not rename (binary serialization)
|
|
|
|
|
+ private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
|
|
|
|
|
+ private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization)
|
|
|
|
|
+ private const string ComparerName = "Comparer"; // Do not rename (binary serialization)
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary() : this(0, null) { }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(int capacity) : this(capacity, null) { }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(int capacity, IEqualityComparer<TKey> comparer)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
|
|
|
|
|
+ if (capacity > 0) Initialize(capacity);
|
|
|
|
|
+ if (comparer != EqualityComparer<TKey>.Default)
|
|
|
|
|
+ {
|
|
|
|
|
+ _comparer = comparer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (typeof(TKey) == typeof(string) && _comparer == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ // To start, move off default comparer for string which is randomised
|
|
|
|
|
+ _comparer = (IEqualityComparer<TKey>)NonRandomizedStringEqualityComparer.Default;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(IDictionary<TKey, TValue> dictionary) : this(dictionary, null) { }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) :
|
|
|
|
|
+ this(dictionary != null ? dictionary.Count : 0, comparer)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dictionary == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // It is likely that the passed-in dictionary is Dictionary<TKey,TValue>. When this is the case,
|
|
|
|
|
+ // avoid the enumerator allocation and overhead by looping through the entries array directly.
|
|
|
|
|
+ // We only do this when dictionary is Dictionary<TKey,TValue> and not a subclass, to maintain
|
|
|
|
|
+ // back-compat with subclasses that may have overridden the enumerator behavior.
|
|
|
|
|
+ if (dictionary.GetType() == typeof(Dictionary<TKey, TValue>))
|
|
|
|
|
+ {
|
|
|
|
|
+ Dictionary<TKey, TValue> d = (Dictionary<TKey, TValue>)dictionary;
|
|
|
|
|
+ int count = d._count;
|
|
|
|
|
+ Entry[] entries = d._entries;
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ Add(entries[i].key, entries[i].value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (KeyValuePair<TKey, TValue> pair in dictionary)
|
|
|
|
|
+ {
|
|
|
|
|
+ Add(pair.Key, pair.Value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) : this(collection, null) { }
|
|
|
|
|
+
|
|
|
|
|
+ public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) :
|
|
|
|
|
+ this((collection as ICollection<KeyValuePair<TKey, TValue>>)?.Count ?? 0, comparer)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (collection == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (KeyValuePair<TKey, TValue> pair in collection)
|
|
|
|
|
+ {
|
|
|
|
|
+ Add(pair.Key, pair.Value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public IEqualityComparer<TKey> Comparer
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ return (_comparer == null || _comparer is NonRandomizedStringEqualityComparer) ? EqualityComparer<TKey>.Default : _comparer;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int Count
|
|
|
|
|
+ {
|
|
|
|
|
+ get { return _count - _freeCount; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public KeyCollection Keys
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_keys == null) _keys = new KeyCollection(this);
|
|
|
|
|
+ return _keys;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ICollection<TKey> IDictionary<TKey, TValue>.Keys
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_keys == null) _keys = new KeyCollection(this);
|
|
|
|
|
+ return _keys;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_keys == null) _keys = new KeyCollection(this);
|
|
|
|
|
+ return _keys;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ValueCollection Values
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_values == null) _values = new ValueCollection(this);
|
|
|
|
|
+ return _values;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ICollection<TValue> IDictionary<TKey, TValue>.Values
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_values == null) _values = new ValueCollection(this);
|
|
|
|
|
+ return _values;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_values == null) _values = new ValueCollection(this);
|
|
|
|
|
+ return _values;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public TValue this[TKey key]
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ int i = FindEntry(key);
|
|
|
|
|
+ if (i >= 0) return _entries[i].value;
|
|
|
|
|
+ ThrowHelper.ThrowKeyNotFoundException(key);
|
|
|
|
|
+ return default;
|
|
|
|
|
+ }
|
|
|
|
|
+ set
|
|
|
|
|
+ {
|
|
|
|
|
+ bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
|
|
|
|
|
+ Debug.Assert(modified);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Add(TKey key, TValue value)
|
|
|
|
|
+ {
|
|
|
|
|
+ bool modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
|
|
|
|
|
+ Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair)
|
|
|
|
|
+ => Add(keyValuePair.Key, keyValuePair.Value);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair)
|
|
|
|
|
+ {
|
|
|
|
|
+ int i = FindEntry(keyValuePair.Key);
|
|
|
|
|
+ if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
|
|
|
|
|
+ {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair)
|
|
|
|
|
+ {
|
|
|
|
|
+ int i = FindEntry(keyValuePair.Key);
|
|
|
|
|
+ if (i >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, keyValuePair.Value))
|
|
|
|
|
+ {
|
|
|
|
|
+ Remove(keyValuePair.Key);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Clear()
|
|
|
|
|
+ {
|
|
|
|
|
+ int count = _count;
|
|
|
|
|
+ if (count > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ Array.Clear(_buckets, 0, _buckets.Length);
|
|
|
|
|
+
|
|
|
|
|
+ _count = 0;
|
|
|
|
|
+ _freeList = -1;
|
|
|
|
|
+ _freeCount = 0;
|
|
|
|
|
+ Array.Clear(_entries, 0, count);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool ContainsKey(TKey key)
|
|
|
|
|
+ => FindEntry(key) >= 0;
|
|
|
|
|
+
|
|
|
|
|
+ public bool ContainsValue(TValue value)
|
|
|
|
|
+ {
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ if (value == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ for (int i = 0; i < _count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ if (default(TValue) != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
|
|
|
|
|
+ for (int i = 0; i < _count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, value)) return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
|
|
|
|
|
+ // https://github.com/dotnet/coreclr/issues/17273
|
|
|
|
|
+ // So cache in a local rather than get EqualityComparer per loop iteration
|
|
|
|
|
+ EqualityComparer<TValue> defaultComparer = EqualityComparer<TValue>.Default;
|
|
|
|
|
+ for (int i = 0; i < _count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (array == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ((uint)index > (uint)array.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (array.Length - index < Count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int count = _count;
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ array[index++] = new KeyValuePair<TKey, TValue>(entries[i].key, entries[i].value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Enumerator GetEnumerator()
|
|
|
|
|
+ => new Enumerator(this, Enumerator.KeyValuePair);
|
|
|
|
|
+
|
|
|
|
|
+ IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
|
|
|
|
+ => new Enumerator(this, Enumerator.KeyValuePair);
|
|
|
|
|
+
|
|
|
|
|
+ private int FindEntry(TKey key)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (key == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int i = -1;
|
|
|
|
|
+ int[] buckets = _buckets;
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ int collisionCount = 0;
|
|
|
|
|
+ if (buckets != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ IEqualityComparer<TKey> comparer = _comparer;
|
|
|
|
|
+ if (comparer == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ int hashCode = key.GetHashCode() & 0x7FFFFFFF;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ i = buckets[hashCode % buckets.Length] - 1;
|
|
|
|
|
+ if (default(TKey) != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test in if to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
|
|
|
|
|
+ // https://github.com/dotnet/coreclr/issues/17273
|
|
|
|
|
+ // So cache in a local rather than get EqualityComparer per loop iteration
|
|
|
|
|
+ EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test in if to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ i = buckets[hashCode % buckets.Length] - 1;
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test in if to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length ||
|
|
|
|
|
+ (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)))
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return i;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int Initialize(int capacity)
|
|
|
|
|
+ {
|
|
|
|
|
+ int size = HashHelpers.GetPrime(capacity);
|
|
|
|
|
+
|
|
|
|
|
+ _freeList = -1;
|
|
|
|
|
+ _buckets = new int[size];
|
|
|
|
|
+ _entries = new Entry[size];
|
|
|
|
|
+
|
|
|
|
|
+ return size;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (key == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (_buckets == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ Initialize(0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ IEqualityComparer<TKey> comparer = _comparer;
|
|
|
|
|
+
|
|
|
|
|
+ int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF;
|
|
|
|
|
+
|
|
|
|
|
+ int collisionCount = 0;
|
|
|
|
|
+ ref int bucket = ref _buckets[hashCode % _buckets.Length];
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ int i = bucket - 1;
|
|
|
|
|
+
|
|
|
|
|
+ if (comparer == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (default(TKey) != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test uint in if rather than loop condition to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (behavior == InsertionBehavior.OverwriteExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ entries[i].value = value;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (behavior == InsertionBehavior.ThrowOnExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
|
|
|
|
|
+ // https://github.com/dotnet/coreclr/issues/17273
|
|
|
|
|
+ // So cache in a local rather than get EqualityComparer per loop iteration
|
|
|
|
|
+ EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test uint in if rather than loop condition to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (behavior == InsertionBehavior.OverwriteExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ entries[i].value = value;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (behavior == InsertionBehavior.ThrowOnExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
|
|
|
|
|
+ // Test uint in if rather than loop condition to drop range check for following array access
|
|
|
|
|
+ if ((uint)i >= (uint)entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (behavior == InsertionBehavior.OverwriteExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ entries[i].value = value;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (behavior == InsertionBehavior.ThrowOnExisting)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i = entries[i].next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ } while (true);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool updateFreeList = false;
|
|
|
|
|
+ int index;
|
|
|
|
|
+ if (_freeCount > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ index = _freeList;
|
|
|
|
|
+ updateFreeList = true;
|
|
|
|
|
+ _freeCount--;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ int count = _count;
|
|
|
|
|
+ if (count == entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ Resize();
|
|
|
|
|
+ bucket = ref _buckets[hashCode % _buckets.Length];
|
|
|
|
|
+ }
|
|
|
|
|
+ index = count;
|
|
|
|
|
+ _count = count + 1;
|
|
|
|
|
+ entries = _entries;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ref Entry entry = ref entries[index];
|
|
|
|
|
+
|
|
|
|
|
+ if (updateFreeList)
|
|
|
|
|
+ {
|
|
|
|
|
+ _freeList = entry.next;
|
|
|
|
|
+ }
|
|
|
|
|
+ entry.hashCode = hashCode;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ entry.next = bucket - 1;
|
|
|
|
|
+ entry.key = key;
|
|
|
|
|
+ entry.value = value;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ bucket = index + 1;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+
|
|
|
|
|
+ // Value types never rehash
|
|
|
|
|
+ if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer)
|
|
|
|
|
+ {
|
|
|
|
|
+ // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
|
|
|
|
|
+ // i.e. EqualityComparer<string>.Default.
|
|
|
|
|
+ _comparer = null;
|
|
|
|
|
+ Resize(entries.Length, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void Resize()
|
|
|
|
|
+ => Resize(HashHelpers.ExpandPrime(_count), false);
|
|
|
|
|
+
|
|
|
|
|
+ private void Resize(int newSize, bool forceNewHashCodes)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Value types never rehash
|
|
|
|
|
+ Debug.Assert(!forceNewHashCodes || default(TKey) == null);
|
|
|
|
|
+ Debug.Assert(newSize >= _entries.Length);
|
|
|
|
|
+
|
|
|
|
|
+ int[] buckets = new int[newSize];
|
|
|
|
|
+ Entry[] entries = new Entry[newSize];
|
|
|
|
|
+
|
|
|
|
|
+ int count = _count;
|
|
|
|
|
+ Array.Copy(_entries, 0, entries, 0, count);
|
|
|
|
|
+
|
|
|
|
|
+ if (default(TKey) == null && forceNewHashCodes)
|
|
|
|
|
+ {
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ Debug.Assert(_comparer == null);
|
|
|
|
|
+ entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ int bucket = entries[i].hashCode % newSize;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ entries[i].next = buckets[bucket] - 1;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ buckets[bucket] = i + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _buckets = buckets;
|
|
|
|
|
+ _entries = entries;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
|
|
|
|
|
+ // statement to copy the value for entry being removed into the output parameter.
|
|
|
|
|
+ // Code has been intentionally duplicated for performance reasons.
|
|
|
|
|
+ public bool Remove(TKey key)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (key == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int[] buckets = _buckets;
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ int collisionCount = 0;
|
|
|
|
|
+ if (buckets != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
|
|
|
|
|
+ int bucket = hashCode % buckets.Length;
|
|
|
|
|
+ int last = -1;
|
|
|
|
|
+ // Value in buckets is 1-based
|
|
|
|
|
+ int i = buckets[bucket] - 1;
|
|
|
|
|
+ while (i >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref entries[i];
|
|
|
|
|
+
|
|
|
|
|
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (last < 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Value in buckets is 1-based
|
|
|
|
|
+ buckets[bucket] = entry.next + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ entries[last].next = entry.next;
|
|
|
|
|
+ }
|
|
|
|
|
+ entry.hashCode = -1;
|
|
|
|
|
+ entry.next = _freeList;
|
|
|
|
|
+
|
|
|
|
|
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
|
|
|
|
|
+ {
|
|
|
|
|
+ entry.key = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
|
|
|
|
|
+ {
|
|
|
|
|
+ entry.value = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ _freeList = i;
|
|
|
|
|
+ _freeCount++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ last = i;
|
|
|
|
|
+ i = entry.next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // This overload is a copy of the overload Remove(TKey key) with one additional
|
|
|
|
|
+ // statement to copy the value for entry being removed into the output parameter.
|
|
|
|
|
+ // Code has been intentionally duplicated for performance reasons.
|
|
|
|
|
+ public bool Remove(TKey key, out TValue value)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (key == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int[] buckets = _buckets;
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ int collisionCount = 0;
|
|
|
|
|
+ if (buckets != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF;
|
|
|
|
|
+ int bucket = hashCode % buckets.Length;
|
|
|
|
|
+ int last = -1;
|
|
|
|
|
+ // Value in buckets is 1-based
|
|
|
|
|
+ int i = buckets[bucket] - 1;
|
|
|
|
|
+ while (i >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref entries[i];
|
|
|
|
|
+
|
|
|
|
|
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (last < 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Value in buckets is 1-based
|
|
|
|
|
+ buckets[bucket] = entry.next + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ entries[last].next = entry.next;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ value = entry.value;
|
|
|
|
|
+
|
|
|
|
|
+ entry.hashCode = -1;
|
|
|
|
|
+ entry.next = _freeList;
|
|
|
|
|
+
|
|
|
|
|
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
|
|
|
|
|
+ {
|
|
|
|
|
+ entry.key = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
|
|
|
|
|
+ {
|
|
|
|
|
+ entry.value = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ _freeList = i;
|
|
|
|
|
+ _freeCount++;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ last = i;
|
|
|
|
|
+ i = entry.next;
|
|
|
|
|
+ if (collisionCount >= entries.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ // The chain of entries forms a loop; which means a concurrent update has happened.
|
|
|
|
|
+ // Break out of the loop and throw, rather than looping forever.
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
|
|
|
|
|
+ }
|
|
|
|
|
+ collisionCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ value = default;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool TryGetValue(TKey key, out TValue value)
|
|
|
|
|
+ {
|
|
|
|
|
+ int i = FindEntry(key);
|
|
|
|
|
+ if (i >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ value = _entries[i].value;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ value = default;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool TryAdd(TKey key, TValue value)
|
|
|
|
|
+ => TryInsert(key, value, InsertionBehavior.None);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
|
|
|
|
|
+ => CopyTo(array, index);
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public int EnsureCapacity(int capacity)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (capacity < 0)
|
|
|
|
|
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
|
|
|
|
|
+ int currentCapacity = _entries == null ? 0 : _entries.Length;
|
|
|
|
|
+ if (currentCapacity >= capacity)
|
|
|
|
|
+ return currentCapacity;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+ if (_buckets == null)
|
|
|
|
|
+ return Initialize(capacity);
|
|
|
|
|
+ int newSize = HashHelpers.GetPrime(capacity);
|
|
|
|
|
+ Resize(newSize, forceNewHashCodes: false);
|
|
|
|
|
+ return newSize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This method can be used to minimize the memory overhead
|
|
|
|
|
+ /// once it is known that no new elements will be added.
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// To allocate minimum size storage array, execute the following statements:
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// dictionary.Clear();
|
|
|
|
|
+ /// dictionary.TrimExcess();
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public void TrimExcess()
|
|
|
|
|
+ => TrimExcess(Count);
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// This method can be used to minimize the memory overhead
|
|
|
|
|
+ /// once it is known that no new elements will be added.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public void TrimExcess(int capacity)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (capacity < Count)
|
|
|
|
|
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
|
|
|
|
|
+ int newSize = HashHelpers.GetPrime(capacity);
|
|
|
|
|
+
|
|
|
|
|
+ Entry[] oldEntries = _entries;
|
|
|
|
|
+ int currentCapacity = oldEntries == null ? 0 : oldEntries.Length;
|
|
|
|
|
+ if (newSize >= currentCapacity)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ int oldCount = _count;
|
|
|
|
|
+ _version++;
|
|
|
|
|
+ Initialize(newSize);
|
|
|
|
|
+ Entry[] entries = _entries;
|
|
|
|
|
+ int[] buckets = _buckets;
|
|
|
|
|
+ int count = 0;
|
|
|
|
|
+ for (int i = 0; i < oldCount; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ int hashCode = oldEntries[i].hashCode;
|
|
|
|
|
+ if (hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref entries[count];
|
|
|
|
|
+ entry = oldEntries[i];
|
|
|
|
|
+ int bucket = hashCode % newSize;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ entry.next = buckets[bucket] - 1;
|
|
|
|
|
+ // Value in _buckets is 1-based
|
|
|
|
|
+ buckets[bucket] = count + 1;
|
|
|
|
|
+ count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ _count = count;
|
|
|
|
|
+ _freeCount = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool IsCompatibleKey(object key)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (key == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
|
|
|
|
|
+ }
|
|
|
|
|
+ return (key is TKey);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly Dictionary<TKey, TValue> _dictionary;
|
|
|
|
|
+ private readonly int _version;
|
|
|
|
|
+ private int _index;
|
|
|
|
|
+ private KeyValuePair<TKey, TValue> _current;
|
|
|
|
|
+ private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
|
|
|
|
|
+
|
|
|
|
|
+ internal const int DictEntry = 1;
|
|
|
|
|
+ internal const int KeyValuePair = 2;
|
|
|
|
|
+
|
|
|
|
|
+ internal Enumerator(Dictionary<TKey, TValue> dictionary, int getEnumeratorRetType)
|
|
|
|
|
+ {
|
|
|
|
|
+ _dictionary = dictionary;
|
|
|
|
|
+ _version = dictionary._version;
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _getEnumeratorRetType = getEnumeratorRetType;
|
|
|
|
|
+ _current = new KeyValuePair<TKey, TValue>();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool MoveNext()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
|
|
|
|
|
+ // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
|
|
|
|
|
+ while ((uint)_index < (uint)_dictionary._count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref _dictionary._entries[_index++];
|
|
|
|
|
+
|
|
|
|
|
+ if (entry.hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ _current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _index = _dictionary._count + 1;
|
|
|
|
|
+ _current = new KeyValuePair<TKey, TValue>();
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public KeyValuePair<TKey, TValue> Current => _current;
|
|
|
|
|
+
|
|
|
|
|
+ public void Dispose()
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Reset()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _current = new KeyValuePair<TKey, TValue>();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))]
|
|
|
|
|
+ [DebuggerDisplay("Count = {Count}")]
|
|
|
|
|
+ public sealed class KeyCollection : ICollection<TKey>, IReadOnlyCollection<TKey>
|
|
|
|
|
+ {
|
|
|
|
|
+ private Dictionary<TKey, TValue> _dictionary;
|
|
|
|
|
+
|
|
|
|
|
+ public KeyCollection(Dictionary<TKey, TValue> dictionary)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dictionary == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
|
|
|
|
|
+ }
|
|
|
|
|
+ _dictionary = dictionary;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Enumerator GetEnumerator()
|
|
|
|
|
+ => new Enumerator(_dictionary);
|
|
|
|
|
+
|
|
|
|
|
+ public void CopyTo(TKey[] array, int index)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (array == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (index < 0 || index > array.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (array.Length - index < _dictionary.Count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int count = _dictionary._count;
|
|
|
|
|
+ Entry[] entries = _dictionary._entries;
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].key;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int Count => _dictionary.Count;
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TKey>.IsReadOnly => true;
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<TKey>.Add(TKey item)
|
|
|
|
|
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<TKey>.Clear()
|
|
|
|
|
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TKey>.Contains(TKey item)
|
|
|
|
|
+ => _dictionary.ContainsKey(item);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TKey>.Remove(TKey item)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator()
|
|
|
|
|
+ => new Enumerator(_dictionary);
|
|
|
|
|
+
|
|
|
|
|
+ public struct Enumerator : IEnumerator<TKey>
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly Dictionary<TKey, TValue> _dictionary;
|
|
|
|
|
+ private int _index;
|
|
|
|
|
+ private readonly int _version;
|
|
|
|
|
+ private TKey _currentKey;
|
|
|
|
|
+
|
|
|
|
|
+ internal Enumerator(Dictionary<TKey, TValue> dictionary)
|
|
|
|
|
+ {
|
|
|
|
|
+ _dictionary = dictionary;
|
|
|
|
|
+ _version = dictionary._version;
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _currentKey = default;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Dispose()
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool MoveNext()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ while ((uint)_index < (uint)_dictionary._count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref _dictionary._entries[_index++];
|
|
|
|
|
+
|
|
|
|
|
+ if (entry.hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ _currentKey = entry.key;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _index = _dictionary._count + 1;
|
|
|
|
|
+ _currentKey = default;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public TKey Current => _currentKey;
|
|
|
|
|
+
|
|
|
|
|
+ public void Reset()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _currentKey = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))]
|
|
|
|
|
+ [DebuggerDisplay("Count = {Count}")]
|
|
|
|
|
+ public sealed class ValueCollection : ICollection<TValue>, IReadOnlyCollection<TValue>
|
|
|
|
|
+ {
|
|
|
|
|
+ private Dictionary<TKey, TValue> _dictionary;
|
|
|
|
|
+
|
|
|
|
|
+ public ValueCollection(Dictionary<TKey, TValue> dictionary)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dictionary == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
|
|
|
|
|
+ }
|
|
|
|
|
+ _dictionary = dictionary;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Enumerator GetEnumerator()
|
|
|
|
|
+ => new Enumerator(_dictionary);
|
|
|
|
|
+
|
|
|
|
|
+ public void CopyTo(TValue[] array, int index)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (array == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (index < 0 || index > array.Length)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (array.Length - index < _dictionary.Count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int count = _dictionary._count;
|
|
|
|
|
+ Entry[] entries = _dictionary._entries;
|
|
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int Count => _dictionary.Count;
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TValue>.IsReadOnly => true;
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<TValue>.Add(TValue item)
|
|
|
|
|
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TValue>.Remove(TValue item)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void ICollection<TValue>.Clear()
|
|
|
|
|
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
|
|
|
|
|
+
|
|
|
|
|
+ bool ICollection<TValue>.Contains(TValue item)
|
|
|
|
|
+ => _dictionary.ContainsValue(item);
|
|
|
|
|
+
|
|
|
|
|
+ IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator()
|
|
|
|
|
+ => new Enumerator(_dictionary);
|
|
|
|
|
+
|
|
|
|
|
+ public struct Enumerator : IEnumerator<TValue>
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly Dictionary<TKey, TValue> _dictionary;
|
|
|
|
|
+ private int _index;
|
|
|
|
|
+ private readonly int _version;
|
|
|
|
|
+ private TValue _currentValue;
|
|
|
|
|
+
|
|
|
|
|
+ internal Enumerator(Dictionary<TKey, TValue> dictionary)
|
|
|
|
|
+ {
|
|
|
|
|
+ _dictionary = dictionary;
|
|
|
|
|
+ _version = dictionary._version;
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _currentValue = default;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Dispose()
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool MoveNext()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ while ((uint)_index < (uint)_dictionary._count)
|
|
|
|
|
+ {
|
|
|
|
|
+ ref Entry entry = ref _dictionary._entries[_index++];
|
|
|
|
|
+
|
|
|
|
|
+ if (entry.hashCode >= 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ _currentValue = entry.value;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ _index = _dictionary._count + 1;
|
|
|
|
|
+ _currentValue = default;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public TValue Current => _currentValue;
|
|
|
|
|
+
|
|
|
|
|
+ public void Reset()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_version != _dictionary._version)
|
|
|
|
|
+ {
|
|
|
|
|
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
|
|
|
|
+ }
|
|
|
|
|
+ _index = 0;
|
|
|
|
|
+ _currentValue = default;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|