// 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. /*============================================================ ** ** ** ** ** Purpose: Generic hash table implementation ** ** #DictionaryVersusHashtableThreadSafety ** Hashtable has multiple reader/single writer (MR/SW) thread safety built into ** certain methods and properties, whereas Dictionary doesn't. If you're ** converting framework code that formerly used Hashtable to Dictionary, it's ** important to consider whether callers may have taken a dependence on MR/SW ** thread safety. If a reader writer lock is available, then that may be used ** with a Dictionary to get the same thread safety guarantee. ** ** Reader writer locks don't exist in silverlight, so we do the following as a ** result of removing non-generic collections from silverlight: ** 1. If the Hashtable was fully synchronized, then we replace it with a ** Dictionary with full locks around reads/writes (same thread safety ** guarantee). ** 2. Otherwise, the Hashtable has the default MR/SW thread safety behavior, ** so we do one of the following on a case-by-case basis: ** a. If the race condition can be addressed by rearranging the code and using a temp ** variable (for example, it's only populated immediately after created) ** then we address the race condition this way and use Dictionary. ** b. If there's concern about degrading performance with the increased ** locking, we ifdef with FEATURE_NONGENERIC_COLLECTIONS so we can at ** least use Hashtable in the desktop build, but Dictionary with full ** locks in silverlight builds. Note that this is heavier locking than ** MR/SW, but this is the only option without rewriting (or adding back) ** the reader writer lock. ** c. If there's no performance concern (e.g. debug-only code) we ** consistently replace Hashtable with Dictionary plus full locks to ** reduce complexity. ** d. Most of serialization is dead code in silverlight. Instead of updating ** those Hashtable occurences in serialization, we carved out references ** to serialization such that this code doesn't need to build in ** silverlight. ===========================================================*/ namespace System.Collections.Generic { using System; using System.Collections; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.Serialization; /// /// Used internally to control behavior of insertion into a . /// internal enum InsertionBehavior : byte { /// /// The default insertion behavior. /// None = 0, /// /// Specifies that an existing entry with the same key should be overwritten if encountered. /// OverwriteExisting = 1, /// /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown. /// ThrowOnExisting = 2 } [DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [Serializable] public class Dictionary : IDictionary, IDictionary, IReadOnlyDictionary, ISerializable, IDeserializationCallback { 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 version; private int freeList; private int freeCount; private IEqualityComparer comparer; private KeyCollection keys; private ValueCollection values; private Object _syncRoot; // constants for serialization private const String VersionName = "Version"; private const String HashSizeName = "HashSize"; // Must save buckets.Length private const String KeyValuePairsName = "KeyValuePairs"; private const String ComparerName = "Comparer"; public Dictionary() : this(0, null) { } public Dictionary(int capacity) : this(capacity, null) { } public Dictionary(IEqualityComparer comparer) : this(0, comparer) { } public Dictionary(int capacity, IEqualityComparer comparer) { if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); if (capacity > 0) Initialize(capacity); this.comparer = comparer ?? EqualityComparer.Default; #if FEATURE_RANDOMIZED_STRING_HASHING if (HashHelpers.s_UseRandomizedStringHashing && this.comparer == EqualityComparer.Default) { this.comparer = (IEqualityComparer)NonRandomizedStringEqualityComparer.Default; } #endif // FEATURE_RANDOMIZED_STRING_HASHING } public Dictionary(IDictionary dictionary) : this(dictionary, null) { } public Dictionary(IDictionary dictionary, IEqualityComparer 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. 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 and not a subclass, to maintain // back-compat with subclasses that may have overridden the enumerator behavior. if (dictionary.GetType() == typeof(Dictionary)) { Dictionary d = (Dictionary)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 pair in dictionary) { Add(pair.Key, pair.Value); } } public Dictionary(IEnumerable> collection) : this(collection, null) { } public Dictionary(IEnumerable> collection, IEqualityComparer comparer) : this((collection as ICollection>)?.Count ?? 0, comparer) { if (collection == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); } foreach (KeyValuePair pair in collection) { Add(pair.Key, pair.Value); } } protected Dictionary(SerializationInfo info, StreamingContext context) { //We can't do anything with the keys and values until the entire graph has been deserialized //and we have a resonable estimate that GetHashCode is not going to fail. For the time being, //we'll just cache this. The graph is not valid until OnDeserialization has been called. HashHelpers.SerializationInfoTable.Add(this, info); } public IEqualityComparer Comparer { get { return comparer; } } public int Count { get { return count - freeCount; } } public KeyCollection Keys { get { Contract.Ensures(Contract.Result() != null); if (keys == null) keys = new KeyCollection(this); return keys; } } ICollection IDictionary.Keys { get { if (keys == null) keys = new KeyCollection(this); return keys; } } IEnumerable IReadOnlyDictionary.Keys { get { if (keys == null) keys = new KeyCollection(this); return keys; } } public ValueCollection Values { get { Contract.Ensures(Contract.Result() != null); if (values == null) values = new ValueCollection(this); return values; } } ICollection IDictionary.Values { get { if (values == null) values = new ValueCollection(this); return values; } } IEnumerable IReadOnlyDictionary.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(); return default(TValue); } 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>.Add(KeyValuePair keyValuePair) { Add(keyValuePair.Key, keyValuePair.Value); } bool ICollection>.Contains(KeyValuePair keyValuePair) { int i = FindEntry(keyValuePair.Key); if (i >= 0 && EqualityComparer.Default.Equals(entries[i].value, keyValuePair.Value)) { return true; } return false; } bool ICollection>.Remove(KeyValuePair keyValuePair) { int i = FindEntry(keyValuePair.Key); if (i >= 0 && EqualityComparer.Default.Equals(entries[i].value, keyValuePair.Value)) { Remove(keyValuePair.Key); return true; } return false; } public void Clear() { if (count > 0) { for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; Array.Clear(entries, 0, count); freeList = -1; count = 0; freeCount = 0; version++; } } public bool ContainsKey(TKey key) { return FindEntry(key) >= 0; } public bool ContainsValue(TValue value) { if (value == null) { for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0 && entries[i].value == null) return true; } } else { EqualityComparer c = EqualityComparer.Default; for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true; } } return false; } private void CopyTo(KeyValuePair[] array, int index) { if (array == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); } if (index < 0 || index > array.Length) { ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); } if (array.Length - index < Count) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); } int count = this.count; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0) { array[index++] = new KeyValuePair(entries[i].key, entries[i].value); } } } public Enumerator GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } IEnumerator> IEnumerable>.GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); } info.AddValue(VersionName, version); info.AddValue(ComparerName, comparer, typeof(IEqualityComparer)); info.AddValue(HashSizeName, buckets == null ? 0 : buckets.Length); //This is the length of the bucket array. if (buckets != null) { KeyValuePair[] array = new KeyValuePair[Count]; CopyTo(array, 0); info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair[])); } } private int FindEntry(TKey key) { if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (buckets != null) { int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) { if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i; } } return -1; } private void Initialize(int capacity) { int size = HashHelpers.GetPrime(capacity); buckets = new int[size]; for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; entries = new Entry[size]; freeList = -1; } private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) { if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } if (buckets == null) Initialize(0); int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; int targetBucket = hashCode % buckets.Length; #if FEATURE_RANDOMIZED_STRING_HASHING int collisionCount = 0; #endif for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) { 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; } #if FEATURE_RANDOMIZED_STRING_HASHING collisionCount++; #endif } int index; if (freeCount > 0) { index = freeList; freeList = entries[index].next; freeCount--; } else { if (count == entries.Length) { Resize(); targetBucket = hashCode % buckets.Length; } index = count; count++; } entries[index].hashCode = hashCode; entries[index].next = buckets[targetBucket]; entries[index].key = key; entries[index].value = value; buckets[targetBucket] = index; version++; #if FEATURE_RANDOMIZED_STRING_HASHING // In case we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing // in this case will be EqualityComparer.Default. // Note, randomized string hashing is turned on by default on coreclr so EqualityComparer.Default will // be using randomized string hashing if (collisionCount > HashHelpers.HashCollisionThreshold && comparer == NonRandomizedStringEqualityComparer.Default) { comparer = (IEqualityComparer)EqualityComparer.Default; Resize(entries.Length, true); } #endif return true; } public virtual void OnDeserialization(Object sender) { SerializationInfo siInfo; HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo); if (siInfo == null) { // It might be necessary to call OnDeserialization from a container if the container object also implements // OnDeserialization. However, remoting will call OnDeserialization again. // We can return immediately if this function is called twice. // Note we set remove the serialization info from the table at the end of this method. return; } int realVersion = siInfo.GetInt32(VersionName); int hashsize = siInfo.GetInt32(HashSizeName); comparer = (IEqualityComparer)siInfo.GetValue(ComparerName, typeof(IEqualityComparer)); if (hashsize != 0) { buckets = new int[hashsize]; for (int i = 0; i < buckets.Length; i++) buckets[i] = -1; entries = new Entry[hashsize]; freeList = -1; KeyValuePair[] array = (KeyValuePair[]) siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair[])); if (array == null) { ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingKeys); } for (int i = 0; i < array.Length; i++) { if (array[i].Key == null) { ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_NullKey); } Add(array[i].Key, array[i].Value); } } else { buckets = null; } version = realVersion; HashHelpers.SerializationInfoTable.Remove(this); } private void Resize() { Resize(HashHelpers.ExpandPrime(count), false); } private void Resize(int newSize, bool forceNewHashCodes) { Debug.Assert(newSize >= entries.Length); int[] newBuckets = new int[newSize]; for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; Entry[] newEntries = new Entry[newSize]; Array.Copy(entries, 0, newEntries, 0, count); if (forceNewHashCodes) { for (int i = 0; i < count; i++) { if (newEntries[i].hashCode != -1) { newEntries[i].hashCode = (comparer.GetHashCode(newEntries[i].key) & 0x7FFFFFFF); } } } for (int i = 0; i < count; i++) { if (newEntries[i].hashCode >= 0) { int bucket = newEntries[i].hashCode % newSize; newEntries[i].next = newBuckets[bucket]; newBuckets[bucket] = i; } } buckets = newBuckets; entries = newEntries; } // 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); } if (buckets != null) { int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; int bucket = hashCode % buckets.Length; int last = -1; for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) { if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { if (last < 0) { buckets[bucket] = entries[i].next; } else { entries[last].next = entries[i].next; } entries[i].hashCode = -1; entries[i].next = freeList; entries[i].key = default(TKey); entries[i].value = default(TValue); freeList = i; freeCount++; version++; return true; } } } 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); } if (buckets != null) { int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; int bucket = hashCode % buckets.Length; int last = -1; for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) { if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) { if (last < 0) { buckets[bucket] = entries[i].next; } else { entries[last].next = entries[i].next; } value = entries[i].value; entries[i].hashCode = -1; entries[i].next = freeList; entries[i].key = default(TKey); entries[i].value = default(TValue); freeList = i; freeCount++; version++; return true; } } } value = default(TValue); 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(TValue); return false; } // Method similar to TryGetValue that returns the value instead of putting it in an out param. public TValue GetValueOrDefault(TKey key) => GetValueOrDefault(key, default(TValue)); // Method similar to TryGetValue that returns the value instead of putting it in an out param. If the entry // doesn't exist, returns the defaultValue instead. public TValue GetValueOrDefault(TKey key, TValue defaultValue) { int i = FindEntry(key); if (i >= 0) { return entries[i].value; } return defaultValue; } public bool TryAdd(TKey key, TValue value) => TryInsert(key, value, InsertionBehavior.None); bool ICollection>.IsReadOnly { get { return false; } } void ICollection>.CopyTo(KeyValuePair[] array, int index) { CopyTo(array, index); } void ICollection.CopyTo(Array array, int index) { if (array == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); } if (array.Rank != 1) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); } if (array.GetLowerBound(0) != 0) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); } if (index < 0 || index > array.Length) { ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); } if (array.Length - index < Count) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); } KeyValuePair[] pairs = array as KeyValuePair[]; if (pairs != null) { CopyTo(pairs, index); } else if (array is DictionaryEntry[]) { DictionaryEntry[] dictEntryArray = array as DictionaryEntry[]; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0) { dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); } } } else { object[] objects = array as object[]; if (objects == null) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } try { int count = this.count; Entry[] entries = this.entries; for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0) { objects[index++] = new KeyValuePair(entries[i].key, entries[i].value); } } } catch (ArrayTypeMismatchException) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } } } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this, Enumerator.KeyValuePair); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { if (_syncRoot == null) { System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); } return _syncRoot; } } bool IDictionary.IsFixedSize { get { return false; } } bool IDictionary.IsReadOnly { get { return false; } } ICollection IDictionary.Keys { get { return (ICollection)Keys; } } ICollection IDictionary.Values { get { return (ICollection)Values; } } object IDictionary.this[object key] { get { if (IsCompatibleKey(key)) { int i = FindEntry((TKey)key); if (i >= 0) { return entries[i].value; } } return null; } set { if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); try { TKey tempKey = (TKey)key; try { this[tempKey] = (TValue)value; } catch (InvalidCastException) { ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue)); } } catch (InvalidCastException) { ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey)); } } } private static bool IsCompatibleKey(object key) { if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } return (key is TKey); } void IDictionary.Add(object key, object value) { if (key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); try { TKey tempKey = (TKey)key; try { Add(tempKey, (TValue)value); } catch (InvalidCastException) { ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue)); } } catch (InvalidCastException) { ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey)); } } bool IDictionary.Contains(object key) { if (IsCompatibleKey(key)) { return ContainsKey((TKey)key); } return false; } IDictionaryEnumerator IDictionary.GetEnumerator() { return new Enumerator(this, Enumerator.DictEntry); } void IDictionary.Remove(object key) { if (IsCompatibleKey(key)) { Remove((TKey)key); } } [Serializable] public struct Enumerator : IEnumerator>, IDictionaryEnumerator { private Dictionary dictionary; private int version; private int index; private KeyValuePair current; private int getEnumeratorRetType; // What should Enumerator.Current return? internal const int DictEntry = 1; internal const int KeyValuePair = 2; internal Enumerator(Dictionary dictionary, int getEnumeratorRetType) { this.dictionary = dictionary; version = dictionary.version; index = 0; this.getEnumeratorRetType = getEnumeratorRetType; current = new KeyValuePair(); } 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 Int32.MaxValue while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].hashCode >= 0) { current = new KeyValuePair(dictionary.entries[index].key, dictionary.entries[index].value); index++; return true; } index++; } index = dictionary.count + 1; current = new KeyValuePair(); return false; } public KeyValuePair Current { get { return current; } } public void Dispose() { } object IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } if (getEnumeratorRetType == DictEntry) { return new System.Collections.DictionaryEntry(current.Key, current.Value); } else { return new KeyValuePair(current.Key, current.Value); } } } void IEnumerator.Reset() { if (version != dictionary.version) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); } index = 0; current = new KeyValuePair(); } DictionaryEntry IDictionaryEnumerator.Entry { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } return new DictionaryEntry(current.Key, current.Value); } } object IDictionaryEnumerator.Key { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } return current.Key; } } object IDictionaryEnumerator.Value { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } return current.Value; } } } [DebuggerTypeProxy(typeof(Mscorlib_DictionaryKeyCollectionDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [Serializable] public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection { private Dictionary dictionary; public KeyCollection(Dictionary dictionary) { if (dictionary == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); } this.dictionary = dictionary; } public Enumerator GetEnumerator() { return 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 { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } void ICollection.Add(TKey item) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); } void ICollection.Clear() { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); } bool ICollection.Contains(TKey item) { return dictionary.ContainsKey(item); } bool ICollection.Remove(TKey item) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet); return false; } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } void ICollection.CopyTo(Array array, int index) { if (array == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); } if (array.Rank != 1) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); } if (array.GetLowerBound(0) != 0) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); } if (index < 0 || index > array.Length) { ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); } if (array.Length - index < dictionary.Count) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); } TKey[] keys = array as TKey[]; if (keys != null) { CopyTo(keys, index); } else { object[] objects = array as object[]; if (objects == null) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } int count = dictionary.count; Entry[] entries = dictionary.entries; try { for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0) objects[index++] = entries[i].key; } } catch (ArrayTypeMismatchException) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } } } bool ICollection.IsSynchronized { get { return false; } } Object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, System.Collections.IEnumerator { private Dictionary dictionary; private int index; private int version; private TKey currentKey; internal Enumerator(Dictionary dictionary) { this.dictionary = dictionary; version = dictionary.version; index = 0; currentKey = default(TKey); } public void Dispose() { } public bool MoveNext() { if (version != dictionary.version) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); } while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].hashCode >= 0) { currentKey = dictionary.entries[index].key; index++; return true; } index++; } index = dictionary.count + 1; currentKey = default(TKey); return false; } public TKey Current { get { return currentKey; } } Object System.Collections.IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } return currentKey; } } void System.Collections.IEnumerator.Reset() { if (version != dictionary.version) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); } index = 0; currentKey = default(TKey); } } } [DebuggerTypeProxy(typeof(Mscorlib_DictionaryValueCollectionDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [Serializable] public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection { private Dictionary dictionary; public ValueCollection(Dictionary dictionary) { if (dictionary == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); } this.dictionary = dictionary; } public Enumerator GetEnumerator() { return 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 { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } void ICollection.Add(TValue item) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); } bool ICollection.Remove(TValue item) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); return false; } void ICollection.Clear() { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet); } bool ICollection.Contains(TValue item) { return dictionary.ContainsValue(item); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(dictionary); } void ICollection.CopyTo(Array array, int index) { if (array == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); } if (array.Rank != 1) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); } if (array.GetLowerBound(0) != 0) { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); } if (index < 0 || index > array.Length) { ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); } if (array.Length - index < dictionary.Count) ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); TValue[] values = array as TValue[]; if (values != null) { CopyTo(values, index); } else { object[] objects = array as object[]; if (objects == null) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } int count = dictionary.count; Entry[] entries = dictionary.entries; try { for (int i = 0; i < count; i++) { if (entries[i].hashCode >= 0) objects[index++] = entries[i].value; } } catch (ArrayTypeMismatchException) { ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType(); } } } bool ICollection.IsSynchronized { get { return false; } } Object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, System.Collections.IEnumerator { private Dictionary dictionary; private int index; private int version; private TValue currentValue; internal Enumerator(Dictionary dictionary) { this.dictionary = dictionary; version = dictionary.version; index = 0; currentValue = default(TValue); } public void Dispose() { } public bool MoveNext() { if (version != dictionary.version) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); } while ((uint)index < (uint)dictionary.count) { if (dictionary.entries[index].hashCode >= 0) { currentValue = dictionary.entries[index].value; index++; return true; } index++; } index = dictionary.count + 1; currentValue = default(TValue); return false; } public TValue Current { get { return currentValue; } } Object System.Collections.IEnumerator.Current { get { if (index == 0 || (index == dictionary.count + 1)) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); } return currentValue; } } void System.Collections.IEnumerator.Reset() { if (version != dictionary.version) { ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); } index = 0; currentValue = default(TValue); } } } } }