//****************************** // Written by Peter Golde // Copyright (c) 2004-2005, Wintellect // // Use and restribution of this code is subject to the license agreement // contained in the file "License.txt" accompanying this file. //****************************** using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace Wintellect.PowerCollections { /// /// DictionaryBase is a base class that can be used to more easily implement the /// generic IDictionary<T> and non-generic IDictionary interfaces. /// /// /// To use DictionaryBase as a base class, the derived class must override /// Count, GetEnumerator, TryGetValue, Clear, Remove, and the indexer set accessor. /// /// The key type of the dictionary. /// The value type of the dictionary. [Serializable] [DebuggerDisplay("{DebuggerDisplayString()}")] public abstract class DictionaryBase : CollectionBase>, IDictionary, IDictionary { /// /// Creates a new DictionaryBase. /// protected DictionaryBase() { } /// /// Clears the dictionary. This method must be overridden in the derived class. /// public abstract override void Clear(); /// /// Removes a key from the dictionary. This method must be overridden in the derived class. /// /// Key to remove from the dictionary. /// True if the key was found, false otherwise. public abstract bool Remove(TKey key); /// /// Determines if this dictionary contains a key equal to . If so, the value /// associated with that key is returned through the value parameter. This method must be /// overridden by the derived class. /// /// The key to search for. /// Returns the value associated with key, if true was returned. /// True if the dictionary contains key. False if the dictionary does not contain key. public abstract bool TryGetValue(TKey key, out TValue value); /// /// Adds a new key-value pair to the dictionary. /// /// The default implementation of this method /// checks to see if the key already exists using /// ContainsKey, then calls the indexer setter if the key doesn't /// already exist. /// Key to add. /// Value to associated with the key. /// key is already present in the dictionary public virtual void Add(TKey key, TValue value) { if (ContainsKey(key)) { throw new ArgumentException(Strings.KeyAlreadyPresent, "key"); } else { this[key] = value; } } /// /// Determines whether a given key is found /// in the dictionary. /// /// The default implementation simply calls TryGetValue and returns /// what it returns. /// Key to look for in the dictionary. /// True if the key is present in the dictionary. public virtual bool ContainsKey(TKey key) { TValue dummy; return TryGetValue(key, out dummy); } /// /// The indexer of the dictionary. This is used to store keys and values and /// retrieve values from the dictionary. The setter /// accessor must be overridden in the derived class. /// /// Key to find in the dictionary. /// The value associated with the key. /// Thrown from the get accessor if the key /// was not found in the dictionary. public virtual TValue this[TKey key] { get { TValue value; if (TryGetValue(key, out value)) return value; else throw new KeyNotFoundException(Strings.KeyNotFound); } set { throw new NotImplementedException(Strings.MustOverrideIndexerSet); } } /// /// Shows the string representation of the dictionary. The string representation contains /// a list of the mappings in the dictionary. /// /// The string representation of the dictionary. public override string ToString() { bool firstItem = true; System.Text.StringBuilder builder = new System.Text.StringBuilder(); builder.Append("{"); // Call ToString on each item and put it in. foreach (KeyValuePair pair in this) { if (!firstItem) builder.Append(", "); if (pair.Key == null) builder.Append("null"); else builder.Append(pair.Key.ToString()); builder.Append("->"); if (pair.Value == null) builder.Append("null"); else builder.Append(pair.Value.ToString()); firstItem = false; } builder.Append("}"); return builder.ToString(); } /// /// Provides a read-only view of this dictionary. The returned IDictionary<TKey,TValue> provides /// a view of the dictionary that prevents modifications to the dictionary. Use the method to provide /// access to the dictionary without allowing changes. Since the returned object is just a view, /// changes to the dictionary will be reflected in the view. /// /// An IIDictionary<TKey,TValue> that provides read-only access to the dictionary. public virtual new IDictionary AsReadOnly() { return Algorithms.ReadOnly(this); } #region IDictionary Members /// /// Returns a collection of the keys in this dictionary. /// /// A read-only collection of the keys in this dictionary. public virtual ICollection Keys { get { return new KeysCollection(this); } } /// /// Returns a collection of the values in this dictionary. The ordering of /// values in this collection is the same as that in the Keys collection. /// /// A read-only collection of the values in this dictionary. public virtual ICollection Values { get { return new ValuesCollection(this); } } #endregion #region ICollection> Members /// /// Adds a key-value pair to the collection. This implementation calls the Add method /// with the Key and Value from the item. /// /// A KeyValuePair contains the Key and Value to add. public override void Add(KeyValuePair item) { this.Add(item.Key, item.Value); } /// /// Determines if a dictionary contains a given KeyValuePair. This implementation checks to see if the /// dictionary contains the given key, and if the value associated with the key is equal to (via object.Equals) /// the value. /// /// A KeyValuePair containing the Key and Value to check for. /// public override bool Contains(KeyValuePair item) { if (this.ContainsKey(item.Key)) { return (object.Equals(this[item.Key], item.Value)); } else { return false; } } /// /// Determines if a dictionary contains a given KeyValuePair, and if so, removes it. This implementation checks to see if the /// dictionary contains the given key, and if the value associated with the key is equal to (via object.Equals) /// the value. If so, the key-value pair is removed. /// /// A KeyValuePair containing the Key and Value to check for. /// True if the item was found and removed. False otherwise. public override bool Remove(KeyValuePair item) { if (((ICollection>)this).Contains(item)) return this.Remove(item.Key); else return false; } #endregion #region IDictionary Members /// /// Check that the given parameter is of the expected generic type. Throw an ArgumentException /// if it isn't. /// /// Expected type of the parameter /// parameter name /// parameter value private void CheckGenericType(string name, object value) { if (!(value is ExpectedType)) throw new ArgumentException(string.Format(Strings.WrongType, value, typeof(ExpectedType)), name); } /// /// Adds a key-value pair to the collection. If key or value are not of the expected types, an /// ArgumentException is thrown. If both key and value are of the expected types, the (overridden) /// Add method is called with the key and value to add. /// /// Key to add to the dictionary. /// Value to add to the dictionary. /// key or value are not of the expected type for this dictionary. void IDictionary.Add(object key, object value) { CheckGenericType("key", key); CheckGenericType("value", value); Add((TKey)key, (TValue)value); } /// /// Clears this dictionary. Calls the (overridden) Clear method. /// void IDictionary.Clear() { this.Clear(); } /// /// Determines if this dictionary contains a key equal to . The dictionary /// is not changed. Calls the (overridden) ContainsKey method. If key is not of the correct /// TKey for the dictionary, false is returned. /// /// The key to search for. /// True if the dictionary contains key. False if the dictionary does not contain key. bool IDictionary.Contains(object key) { if (key is TKey || key == null) return ContainsKey((TKey)key); else return false; } /// /// Removes the key (and associated value) from the collection that is equal to the passed in key. If /// no key in the dictionary is equal to the passed key, the /// dictionary is unchanged. Calls the (overridden) Remove method. If key is not of the correct /// TKey for the dictionary, the dictionary is unchanged. /// /// The key to remove. /// key could not be converted to TKey. void IDictionary.Remove(object key) { if (key is TKey || key == null) Remove((TKey)key); } /// /// Returns an enumerator that enumerates all the entries in the dictionary. Each entry is /// returned as a DictionaryEntry. /// The entries are enumerated in the same orders as the (overridden) GetEnumerator /// method. /// /// An enumerator for enumerating all the elements in the OrderedDictionary. IDictionaryEnumerator IDictionary.GetEnumerator() { // You can't implement this directly with an iterator, because iterators automatically implement // IEnumerator, not IDictionaryEnumerator. We use the helper class DictionaryEnumeratorWrapper. return new DictionaryEnumeratorWrapper(this.GetEnumerator()); } /// /// Returns an enumerator that enumerates all the entries in the dictionary. Each entry is /// returned as a DictionaryEntry. /// The entries are enumerated in the same orders as the (overridden) GetEnumerator /// method. /// /// An enumerator for enumerating all the elements in the OrderedDictionary. IEnumerator IEnumerable.GetEnumerator() { return ((IDictionary)this).GetEnumerator(); } /// /// Returns whether this dictionary is fixed size. This implemented always returns false. /// /// Always returns false. bool IDictionary.IsFixedSize { get { return false; } } /// /// Returns if this dictionary is read-only. This implementation always returns false. /// /// Always returns false. bool IDictionary.IsReadOnly { get { return false; } } /// /// Returns a collection of all the keys in the dictionary. The values in this collection will /// be enumerated in the same order as the (overridden) GetEnumerator method. /// /// The collection of keys. ICollection IDictionary.Keys { get { return new KeysCollection(this); } } /// /// Returns a collection of all the values in the dictionary. The values in this collection will /// be enumerated in the same order as the (overridden) GetEnumerator method. /// /// The collection of values. ICollection IDictionary.Values { get { return new ValuesCollection(this); } } /// /// Gets or sets the value associated with a given key. When getting a value, if this /// key is not found in the collection, then null is returned. When setting /// a value, the value replaces any existing value in the dictionary. If either the key or value /// are not of the correct type for this dictionary, an ArgumentException is thrown. /// /// The value associated with the key, or null if the key was not present. /// key could not be converted to TKey, or value could not be converted to TValue. object IDictionary.this[object key] { get { if (key is TKey || key == null) { TKey theKey = (TKey)key; TValue theValue; // The IDictionary (non-generic) indexer returns null for not found, instead of // throwing an exception like the generic IDictionary indexer. if (TryGetValue(theKey, out theValue)) return theValue; else return null; } else { return null; } } set { CheckGenericType("key", key); CheckGenericType("value", value); this[(TKey)key] = (TValue)value; } } #endregion /// /// Display the contents of the dictionary in the debugger. This is intentionally private, it is called /// only from the debugger due to the presence of the DebuggerDisplay attribute. It is similar /// format to ToString(), but is limited to 250-300 characters or so, so as not to overload the debugger. /// /// The string representation of the items in the collection, similar in format to ToString(). new internal string DebuggerDisplayString() { const int MAXLENGTH = 250; bool firstItem = true; System.Text.StringBuilder builder = new System.Text.StringBuilder(); builder.Append("{"); // Call ToString on each item and put it in. foreach (KeyValuePair pair in this) { if (builder.Length >= MAXLENGTH) { builder.Append(", ..."); break; } if (!firstItem) builder.Append(", "); if (pair.Key == null) builder.Append("null"); else builder.Append(pair.Key.ToString()); builder.Append("->"); if (pair.Value == null) builder.Append("null"); else builder.Append(pair.Value.ToString()); firstItem = false; } builder.Append("}"); return builder.ToString(); } #region Keys and Values collections /// /// A private class that implements ICollection<TKey> and ICollection for the /// Keys collection. The collection is read-only. /// [Serializable] private sealed class KeysCollection : ReadOnlyCollectionBase { private DictionaryBase myDictionary; /// /// Constructor. /// /// The dictionary this is associated with. public KeysCollection(DictionaryBase myDictionary) { this.myDictionary = myDictionary; } public override int Count { get { return myDictionary.Count; } } public override IEnumerator GetEnumerator() { foreach (KeyValuePair pair in myDictionary) yield return pair.Key; } public override bool Contains(TKey key) { return myDictionary.ContainsKey(key); } } /// /// A private class that implements ICollection<TValue> and ICollection for the /// Values collection. The collection is read-only. /// [Serializable] private sealed class ValuesCollection : ReadOnlyCollectionBase { private DictionaryBase myDictionary; public ValuesCollection(DictionaryBase myDictionary) { this.myDictionary = myDictionary; } public override int Count { get { return myDictionary.Count; } } public override IEnumerator GetEnumerator() { foreach (KeyValuePair pair in myDictionary) yield return pair.Value; } } #endregion /// /// A class that wraps a IDictionaryEnumerator around an IEnumerator that /// enumerates KeyValuePairs. This is useful in implementing IDictionary, because /// IEnumerator can be implemented with an iterator, but IDictionaryEnumerator cannot. /// [Serializable] private class DictionaryEnumeratorWrapper : IDictionaryEnumerator { private IEnumerator> enumerator; /// /// Constructor. /// /// The enumerator of KeyValuePairs that is being wrapped. public DictionaryEnumeratorWrapper(IEnumerator> enumerator) { this.enumerator = enumerator; } public DictionaryEntry Entry { get { KeyValuePair pair = enumerator.Current; DictionaryEntry entry = new DictionaryEntry(); if (pair.Key != null) entry.Key = pair.Key; entry.Value = pair.Value; return entry; } } public object Key { get { KeyValuePair pair = enumerator.Current; return pair.Key; } } public object Value { get { KeyValuePair pair = enumerator.Current; return pair.Value; } } public void Reset() { throw new NotSupportedException(Strings.ResetNotSupported); } public bool MoveNext() { return enumerator.MoveNext(); } public object Current { get { return Entry; } } } } }