// // System.Collections.Generic.Dictionary // // Authors: // Sureshkumar T (tsureshkumar@novell.com) // Marek Safar (marek.safar@gmail.com) // Ankit Jain (radical@corewars.org) // David Waite (mass@akuma.org) // Juraj Skripsky (js@hotfeet.ch) // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // Copyright (C) 2005 David Waite // Copyright (C) 2007 HotFeet GmbH (http://www.hotfeet.ch) // Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization; using System.Security.Permissions; using System.Runtime.InteropServices; using System.Diagnostics; namespace System.Collections.Generic { /* * Declare this outside the main class so it doesn't have to be inflated for each * instantiation of Dictionary. */ internal struct Link { public int HashCode; public int Next; } [ComVisible(false)] [Serializable] [DebuggerDisplay ("Count={Count}")] [DebuggerTypeProxy (typeof (CollectionDebuggerView<,>))] public class Dictionary : IDictionary, IDictionary, ISerializable, IDeserializationCallback #if NET_4_5 , IReadOnlyDictionary #endif { // The implementation of this class uses a hash table and linked lists // (see: http://msdn2.microsoft.com/en-us/library/ms379571(VS.80).aspx). // // We use a kind of "mini-heap" instead of reference-based linked lists: // "keySlots" and "valueSlots" is the heap itself, it stores the data // "linkSlots" contains information about how the slots in the heap // are connected into linked lists // In addition, the HashCode field can be used to check if the // corresponding key and value are present (HashCode has the // HASH_FLAG bit set in this case), so, to iterate over all the // items in the dictionary, simply iterate the linkSlots array // and check for the HASH_FLAG bit in the HashCode field. // For this reason, each time a hashcode is calculated, it needs // to be ORed with HASH_FLAG before comparing it with the save hashcode. // "touchedSlots" and "emptySlot" manage the free space in the heap const int INITIAL_SIZE = 10; const float DEFAULT_LOAD_FACTOR = (90f / 100); const int NO_SLOT = -1; const int HASH_FLAG = -2147483648; // The hash table contains indices into the linkSlots array int [] table; // All (key,value) pairs are chained into linked lists. The connection // information is stored in "linkSlots" along with the key's hash code // (for performance reasons). // TODO: get rid of the hash code in Link (this depends on a few // JIT-compiler optimizations) // Every link in "linkSlots" corresponds to the (key,value) pair // in "keySlots"/"valueSlots" with the same index. Link [] linkSlots; TKey [] keySlots; TValue [] valueSlots; //Leave those 2 fields here to improve heap layout. IEqualityComparer hcp; SerializationInfo serialization_info; // The number of slots in "linkSlots" and "keySlots"/"valueSlots" that // are in use (i.e. filled with data) or have been used and marked as // "empty" later on. int touchedSlots; // The index of the first slot in the "empty slots chain". // "Remove()" prepends the cleared slots to the empty chain. // "Add()" fills the first slot in the empty slots chain with the // added item (or increases "touchedSlots" if the chain itself is empty). int emptySlot; // The number of (key,value) pairs in this dictionary. int count; // The number of (key,value) pairs the dictionary can hold without // resizing the hash table and the slots arrays. int threshold; // The number of changes made to this dictionary. Used by enumerators // to detect changes and invalidate themselves. int generation; public int Count { get { return count; } } public TValue this [TKey key] { get { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int cur = table [(hashCode & int.MaxValue) % table.Length] - 1; // walk linked list until right slot is found or end is reached while (cur != NO_SLOT) { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) return valueSlots [cur]; cur = linkSlots [cur].Next; } throw new KeyNotFoundException (); } set { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int index = (hashCode & int.MaxValue) % table.Length; int cur = table [index] - 1; // walk linked list until right slot (and its predecessor) is // found or end is reached int prev = NO_SLOT; if (cur != NO_SLOT) { do { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) break; prev = cur; cur = linkSlots [cur].Next; } while (cur != NO_SLOT); } // is there no slot for the given key yet? if (cur == NO_SLOT) { // there is no existing slot for the given key, // allocate one and prepend it to its corresponding linked // list if (++count > threshold) { Resize (); index = (hashCode & int.MaxValue) % table.Length; } // find an empty slot cur = emptySlot; if (cur == NO_SLOT) cur = touchedSlots++; else emptySlot = linkSlots [cur].Next; // prepend the added item to its linked list, // update the hash table linkSlots [cur].Next = table [index] - 1; table [index] = cur + 1; // store the new item and its hash code linkSlots [cur].HashCode = hashCode; keySlots [cur] = key; } else { // we already have a slot for the given key, // update the existing slot // if the slot is not at the front of its linked list, // we move it there if (prev != NO_SLOT) { linkSlots [prev].Next = linkSlots [cur].Next; linkSlots [cur].Next = table [index] - 1; table [index] = cur + 1; } } // store the item's data itself valueSlots [cur] = value; generation++; } } public Dictionary () { Init (INITIAL_SIZE, null); } public Dictionary (IEqualityComparer comparer) { Init (INITIAL_SIZE, comparer); } public Dictionary (IDictionary dictionary) : this (dictionary, null) { } public Dictionary (int capacity) { Init (capacity, null); } public Dictionary (IDictionary dictionary, IEqualityComparer comparer) { if (dictionary == null) throw new ArgumentNullException ("dictionary"); int capacity = dictionary.Count; Init (capacity, comparer); foreach (KeyValuePair entry in dictionary) this.Add (entry.Key, entry.Value); } public Dictionary (int capacity, IEqualityComparer comparer) { Init (capacity, comparer); } protected Dictionary (SerializationInfo info, StreamingContext context) { serialization_info = info; } private void Init (int capacity, IEqualityComparer hcp) { if (capacity < 0) throw new ArgumentOutOfRangeException ("capacity"); this.hcp = (hcp != null) ? hcp : EqualityComparer.Default; if (capacity == 0) capacity = INITIAL_SIZE; /* Modify capacity so 'capacity' elements can be added without resizing */ capacity = (int)(capacity / DEFAULT_LOAD_FACTOR) + 1; InitArrays (capacity); generation = 0; } private void InitArrays (int size) { table = new int [size]; linkSlots = new Link [size]; emptySlot = NO_SLOT; keySlots = new TKey [size]; valueSlots = new TValue [size]; touchedSlots = 0; threshold = (int)(table.Length * DEFAULT_LOAD_FACTOR); if (threshold == 0 && table.Length > 0) threshold = 1; } void CopyToCheck (Array array, int index) { if (array == null) throw new ArgumentNullException ("array"); if (index < 0) throw new ArgumentOutOfRangeException ("index"); // we want no exception for index==array.Length && Count == 0 if (index > array.Length) throw new ArgumentException ("index larger than largest valid index of array"); if (array.Length - index < Count) throw new ArgumentException ("Destination array cannot hold the requested elements!"); } void CopyKeys (TKey[] array, int index) { for (int i = 0; i < touchedSlots; i++) { if ((linkSlots [i].HashCode & HASH_FLAG) != 0) array [index++] = keySlots [i]; } } void CopyValues (TValue[] array, int index) { for (int i = 0; i < touchedSlots; i++) { if ((linkSlots [i].HashCode & HASH_FLAG) != 0) array [index++] = valueSlots [i]; } } delegate TRet Transform (TKey key, TValue value); static KeyValuePair make_pair (TKey key, TValue value) { return new KeyValuePair (key, value); } static TKey pick_key (TKey key, TValue value) { return key; } static TValue pick_value (TKey key, TValue value) { return value; } void CopyTo (KeyValuePair [] array, int index) { CopyToCheck (array, index); for (int i = 0; i < touchedSlots; i++) { if ((linkSlots [i].HashCode & HASH_FLAG) != 0) array [index++] = new KeyValuePair (keySlots [i], valueSlots [i]); } } void Do_ICollectionCopyTo (Array array, int index, Transform transform) { Type src = typeof (TRet); Type tgt = array.GetType ().GetElementType (); try { if ((src.IsPrimitive || tgt.IsPrimitive) && !tgt.IsAssignableFrom (src)) throw new Exception (); // we don't care. it'll get transformed to an ArgumentException below #if BOOTSTRAP_BASIC // BOOTSTRAP: gmcs 2.4.x seems to have trouble compiling the alternative throw new Exception (); #else object[] dest = (object[])array; for (int i = 0; i < touchedSlots; i++) { if ((linkSlots [i].HashCode & HASH_FLAG) != 0) dest [index++] = transform (keySlots [i], valueSlots [i]); } #endif } catch (Exception e) { throw new ArgumentException ("Cannot copy source collection elements to destination array", "array", e); } } private void Resize () { // From the SDK docs: // Hashtable is automatically increased // to the smallest prime number that is larger // than twice the current number of Hashtable buckets int newSize = HashPrimeNumbers.ToPrime ((table.Length << 1) | 1); // allocate new hash table and link slots array int [] newTable = new int [newSize]; Link [] newLinkSlots = new Link [newSize]; for (int i = 0; i < table.Length; i++) { int cur = table [i] - 1; while (cur != NO_SLOT) { int hashCode = newLinkSlots [cur].HashCode = hcp.GetHashCode(keySlots [cur]) | HASH_FLAG; int index = (hashCode & int.MaxValue) % newSize; newLinkSlots [cur].Next = newTable [index] - 1; newTable [index] = cur + 1; cur = linkSlots [cur].Next; } } table = newTable; linkSlots = newLinkSlots; // allocate new data slots, copy data TKey [] newKeySlots = new TKey [newSize]; TValue [] newValueSlots = new TValue [newSize]; Array.Copy (keySlots, 0, newKeySlots, 0, touchedSlots); Array.Copy (valueSlots, 0, newValueSlots, 0, touchedSlots); keySlots = newKeySlots; valueSlots = newValueSlots; threshold = (int)(newSize * DEFAULT_LOAD_FACTOR); } public void Add (TKey key, TValue value) { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int index = (hashCode & int.MaxValue) % table.Length; int cur = table [index] - 1; // walk linked list until end is reached (throw an exception if a // existing slot is found having an equivalent key) while (cur != NO_SLOT) { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) throw new ArgumentException ("An element with the same key already exists in the dictionary."); cur = linkSlots [cur].Next; } if (++count > threshold) { Resize (); index = (hashCode & int.MaxValue) % table.Length; } // find an empty slot cur = emptySlot; if (cur == NO_SLOT) cur = touchedSlots++; else emptySlot = linkSlots [cur].Next; // store the hash code of the added item, // prepend the added item to its linked list, // update the hash table linkSlots [cur].HashCode = hashCode; linkSlots [cur].Next = table [index] - 1; table [index] = cur + 1; // store item's data keySlots [cur] = key; valueSlots [cur] = value; generation++; } public IEqualityComparer Comparer { get { return hcp; } } public void Clear () { count = 0; // clear the hash table Array.Clear (table, 0, table.Length); // clear arrays Array.Clear (keySlots, 0, keySlots.Length); Array.Clear (valueSlots, 0, valueSlots.Length); Array.Clear (linkSlots, 0, linkSlots.Length); // empty the "empty slots chain" emptySlot = NO_SLOT; touchedSlots = 0; generation++; } public bool ContainsKey (TKey key) { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int cur = table [(hashCode & int.MaxValue) % table.Length] - 1; // walk linked list until right slot is found or end is reached while (cur != NO_SLOT) { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) return true; cur = linkSlots [cur].Next; } return false; } public bool ContainsValue (TValue value) { IEqualityComparer cmp = EqualityComparer.Default; for (int i = 0; i < table.Length; i++) { int cur = table [i] - 1; while (cur != NO_SLOT) { if (cmp.Equals (valueSlots [cur], value)) return true; cur = linkSlots [cur].Next; } } return false; } [SecurityPermission (SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)] public virtual void GetObjectData (SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException ("info"); info.AddValue ("Version", generation); info.AddValue ("Comparer", hcp); // MS.NET expects either *no* KeyValuePairs field (when count = 0) // or a non-null KeyValuePairs field. We don't omit the field to // remain compatible with older monos, but we also doesn't serialize // it as null to make MS.NET happy. KeyValuePair [] data = new KeyValuePair [count]; if (count > 0) CopyTo (data, 0); info.AddValue ("HashSize", table.Length); info.AddValue ("KeyValuePairs", data); } public virtual void OnDeserialization (object sender) { if (serialization_info == null) return; int hashSize = 0; KeyValuePair [] data = null; // We must use the enumerator because MS.NET doesn't // serialize "KeyValuePairs" for count = 0. SerializationInfoEnumerator e = serialization_info.GetEnumerator (); while (e.MoveNext ()) { switch (e.Name) { case "Version": generation = (int) e.Value; break; case "Comparer": hcp = (IEqualityComparer) e.Value; break; case "HashSize": hashSize = (int) e.Value; break; case "KeyValuePairs": data = (KeyValuePair []) e.Value; break; } } if (hcp == null) hcp = EqualityComparer.Default; if (hashSize < INITIAL_SIZE) hashSize = INITIAL_SIZE; InitArrays (hashSize); count = 0; if (data != null) { for (int i = 0; i < data.Length; ++i) Add (data [i].Key, data [i].Value); } generation++; serialization_info = null; } public bool Remove (TKey key) { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int index = (hashCode & int.MaxValue) % table.Length; int cur = table [index] - 1; // if there is no linked list, return false if (cur == NO_SLOT) return false; // walk linked list until right slot (and its predecessor) is // found or end is reached int prev = NO_SLOT; do { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) break; prev = cur; cur = linkSlots [cur].Next; } while (cur != NO_SLOT); // if we reached the end of the chain, return false if (cur == NO_SLOT) return false; count--; // remove slot from linked list // is slot at beginning of linked list? if (prev == NO_SLOT) table [index] = linkSlots [cur].Next + 1; else linkSlots [prev].Next = linkSlots [cur].Next; // mark slot as empty and prepend it to "empty slots chain" linkSlots [cur].Next = emptySlot; emptySlot = cur; linkSlots [cur].HashCode = 0; // clear empty key and value slots keySlots [cur] = default (TKey); valueSlots [cur] = default (TValue); generation++; return true; } public bool TryGetValue (TKey key, out TValue value) { if (key == null) throw new ArgumentNullException ("key"); // get first item of linked list corresponding to given key int hashCode = hcp.GetHashCode (key) | HASH_FLAG; int cur = table [(hashCode & int.MaxValue) % table.Length] - 1; // walk linked list until right slot is found or end is reached while (cur != NO_SLOT) { // The ordering is important for compatibility with MS and strange // Object.Equals () implementations if (linkSlots [cur].HashCode == hashCode && hcp.Equals (keySlots [cur], key)) { value = valueSlots [cur]; return true; } cur = linkSlots [cur].Next; } // we did not find the slot value = default (TValue); return false; } ICollection IDictionary.Keys { get { return Keys; } } ICollection IDictionary.Values { get { return Values; } } #if NET_4_5 IEnumerable IReadOnlyDictionary.Keys { get { return Keys; } } IEnumerable IReadOnlyDictionary.Values { get { return Values; } } #endif public KeyCollection Keys { get { return new KeyCollection (this); } } public ValueCollection Values { get { return new ValueCollection (this); } } ICollection IDictionary.Keys { get { return Keys; } } ICollection IDictionary.Values { get { return Values; } } bool IDictionary.IsFixedSize { get { return false; } } bool IDictionary.IsReadOnly { get { return false; } } static TKey ToTKey (object key) { if (key == null) throw new ArgumentNullException ("key"); if (!(key is TKey)) throw new ArgumentException ("not of type: " + typeof (TKey).ToString (), "key"); return (TKey) key; } static TValue ToTValue (object value) { if (value == null && !typeof (TValue).IsValueType) return default (TValue); if (!(value is TValue)) throw new ArgumentException ("not of type: " + typeof (TValue).ToString (), "value"); return (TValue) value; } object IDictionary.this [object key] { get { if (key is TKey && ContainsKey((TKey) key)) return this [ToTKey (key)]; return null; } set { this [ToTKey (key)] = ToTValue (value); } } void IDictionary.Add (object key, object value) { this.Add (ToTKey (key), ToTValue (value)); } bool IDictionary.Contains (object key) { if (key == null) throw new ArgumentNullException ("key"); if (key is TKey) return ContainsKey ((TKey) key); return false; } void IDictionary.Remove (object key) { if (key == null) throw new ArgumentNullException ("key"); if (key is TKey) Remove ((TKey) key); } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { return this; } } bool ICollection>.IsReadOnly { get { return false; } } void ICollection>.Add (KeyValuePair keyValuePair) { Add (keyValuePair.Key, keyValuePair.Value); } bool ICollection>.Contains (KeyValuePair keyValuePair) { return ContainsKeyValuePair (keyValuePair); } void ICollection>.CopyTo (KeyValuePair [] array, int index) { this.CopyTo (array, index); } bool ICollection>.Remove (KeyValuePair keyValuePair) { if (!ContainsKeyValuePair (keyValuePair)) return false; return Remove (keyValuePair.Key); } bool ContainsKeyValuePair (KeyValuePair pair) { TValue value; if (!TryGetValue (pair.Key, out value)) return false; return EqualityComparer.Default.Equals (pair.Value, value); } void ICollection.CopyTo (Array array, int index) { KeyValuePair [] pairs = array as KeyValuePair []; if (pairs != null) { this.CopyTo (pairs, index); return; } CopyToCheck (array, index); DictionaryEntry [] entries = array as DictionaryEntry []; if (entries != null) { for (int i = 0; i < touchedSlots; i++) { if ((linkSlots [i].HashCode & HASH_FLAG) != 0) entries [index++] = new DictionaryEntry (keySlots [i], valueSlots [i]); } return; } Do_ICollectionCopyTo> (array, index, make_pair); } IEnumerator IEnumerable.GetEnumerator () { return new Enumerator (this); } IEnumerator> IEnumerable>.GetEnumerator () { return new Enumerator (this); } IDictionaryEnumerator IDictionary.GetEnumerator () { return new ShimEnumerator (this); } public Enumerator GetEnumerator () { return new Enumerator (this); } [Serializable] private class ShimEnumerator : IDictionaryEnumerator, IEnumerator { Enumerator host_enumerator; public ShimEnumerator (Dictionary host) { host_enumerator = host.GetEnumerator (); } public void Dispose () { host_enumerator.Dispose (); } public bool MoveNext () { return host_enumerator.MoveNext (); } public DictionaryEntry Entry { get { return ((IDictionaryEnumerator) host_enumerator).Entry; } } public object Key { get { return host_enumerator.Current.Key; } } public object Value { get { return host_enumerator.Current.Value; } } // This is the raison d' etre of this $%!@$%@^@ class. // We want: IDictionary.GetEnumerator ().Current is DictionaryEntry public object Current { get { return Entry; } } public void Reset () { host_enumerator.Reset (); } } [Serializable] public struct Enumerator : IEnumerator>, IDisposable, IDictionaryEnumerator, IEnumerator { Dictionary dictionary; int next; int stamp; internal KeyValuePair current; internal Enumerator (Dictionary dictionary) : this () { this.dictionary = dictionary; stamp = dictionary.generation; } public bool MoveNext () { VerifyState (); if (next < 0) return false; while (next < dictionary.touchedSlots) { int cur = next++; if ((dictionary.linkSlots [cur].HashCode & HASH_FLAG) != 0) { current = new KeyValuePair ( dictionary.keySlots [cur], dictionary.valueSlots [cur] ); return true; } } next = -1; return false; } // No error checking happens. Usually, Current is immediately preceded by a MoveNext(), so it's wasteful to check again public KeyValuePair Current { get { return current; } } internal TKey CurrentKey { get { VerifyCurrent (); return current.Key; } } internal TValue CurrentValue { get { VerifyCurrent (); return current.Value; } } object IEnumerator.Current { get { VerifyCurrent (); return current; } } void IEnumerator.Reset () { Reset (); } internal void Reset () { VerifyState (); next = 0; } DictionaryEntry IDictionaryEnumerator.Entry { get { VerifyCurrent (); return new DictionaryEntry (current.Key, current.Value); } } object IDictionaryEnumerator.Key { get { return CurrentKey; } } object IDictionaryEnumerator.Value { get { return CurrentValue; } } void VerifyState () { if (dictionary == null) throw new ObjectDisposedException (null); if (dictionary.generation != stamp) throw new InvalidOperationException ("out of sync"); } void VerifyCurrent () { VerifyState (); if (next <= 0) throw new InvalidOperationException ("Current is not valid"); } public void Dispose () { dictionary = null; } } // This collection is a read only collection [Serializable] [DebuggerDisplay ("Count={Count}")] [DebuggerTypeProxy (typeof (CollectionDebuggerView<,>))] public sealed class KeyCollection : ICollection, IEnumerable, ICollection, IEnumerable { Dictionary dictionary; public KeyCollection (Dictionary dictionary) { if (dictionary == null) throw new ArgumentNullException ("dictionary"); this.dictionary = dictionary; } public void CopyTo (TKey [] array, int index) { dictionary.CopyToCheck (array, index); dictionary.CopyKeys (array, index); } public Enumerator GetEnumerator () { return new Enumerator (dictionary); } void ICollection.Add (TKey item) { throw new NotSupportedException ("this is a read-only collection"); } void ICollection.Clear () { throw new NotSupportedException ("this is a read-only collection"); } bool ICollection.Contains (TKey item) { return dictionary.ContainsKey (item); } bool ICollection.Remove (TKey item) { throw new NotSupportedException ("this is a read-only collection"); } IEnumerator IEnumerable.GetEnumerator () { return this.GetEnumerator (); } void ICollection.CopyTo (Array array, int index) { var target = array as TKey []; if (target != null) { CopyTo (target, index); return; } dictionary.CopyToCheck (array, index); dictionary.Do_ICollectionCopyTo (array, index, pick_key); } IEnumerator IEnumerable.GetEnumerator () { return this.GetEnumerator (); } public int Count { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { return ((ICollection) dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, IDisposable, IEnumerator { Dictionary.Enumerator host_enumerator; internal Enumerator (Dictionary host) { host_enumerator = host.GetEnumerator (); } public void Dispose () { host_enumerator.Dispose (); } public bool MoveNext () { return host_enumerator.MoveNext (); } public TKey Current { get { return host_enumerator.current.Key; } } object IEnumerator.Current { get { return host_enumerator.CurrentKey; } } void IEnumerator.Reset () { host_enumerator.Reset (); } } } // This collection is a read only collection [Serializable] [DebuggerDisplay ("Count={Count}")] [DebuggerTypeProxy (typeof (CollectionDebuggerView<,>))] public sealed class ValueCollection : ICollection, IEnumerable, ICollection, IEnumerable { Dictionary dictionary; public ValueCollection (Dictionary dictionary) { if (dictionary == null) throw new ArgumentNullException ("dictionary"); this.dictionary = dictionary; } public void CopyTo (TValue [] array, int index) { dictionary.CopyToCheck (array, index); dictionary.CopyValues (array, index); } public Enumerator GetEnumerator () { return new Enumerator (dictionary); } void ICollection.Add (TValue item) { throw new NotSupportedException ("this is a read-only collection"); } void ICollection.Clear () { throw new NotSupportedException ("this is a read-only collection"); } bool ICollection.Contains (TValue item) { return dictionary.ContainsValue (item); } bool ICollection.Remove (TValue item) { throw new NotSupportedException ("this is a read-only collection"); } IEnumerator IEnumerable.GetEnumerator () { return this.GetEnumerator (); } void ICollection.CopyTo (Array array, int index) { var target = array as TValue []; if (target != null) { CopyTo (target, index); return; } dictionary.CopyToCheck (array, index); dictionary.Do_ICollectionCopyTo (array, index, pick_value); } IEnumerator IEnumerable.GetEnumerator () { return this.GetEnumerator (); } public int Count { get { return dictionary.Count; } } bool ICollection.IsReadOnly { get { return true; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { return ((ICollection) dictionary).SyncRoot; } } [Serializable] public struct Enumerator : IEnumerator, IDisposable, IEnumerator { Dictionary.Enumerator host_enumerator; internal Enumerator (Dictionary host) { host_enumerator = host.GetEnumerator (); } public void Dispose () { host_enumerator.Dispose (); } public bool MoveNext () { return host_enumerator.MoveNext (); } public TValue Current { get { return host_enumerator.current.Value; } } object IEnumerator.Current { get { return host_enumerator.CurrentValue; } } void IEnumerator.Reset () { host_enumerator.Reset (); } } } } }