//****************************** // 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 { /// /// ReadOnlyDictionaryBase is a base class that can be used to more easily implement the /// generic IDictionary<T> and non-generic IDictionary interfaces. /// /// /// To use ReadOnlyDictionaryBase as a base class, the derived class must override /// Count, TryGetValue, GetEnumerator. /// /// The key type of the dictionary. /// The value type of the dictionary. [Serializable] [DebuggerDisplay("{DebuggerDisplayString()}")] public abstract class ReadOnlyDictionaryBase : ReadOnlyCollectionBase>, IDictionary, IDictionary { /// /// Creates a new DictionaryBase. This must be called from the constructor of the /// derived class to specify whether the dictionary is read-only and the name of the /// collection. /// protected ReadOnlyDictionaryBase() { } /// /// Throws an NotSupportedException stating that this collection cannot be modified. /// private void MethodModifiesCollection() { throw new NotSupportedException(string.Format(Strings.CannotModifyCollection, Util.SimpleClassName(this.GetType()))); } /// /// Adds a new key-value pair to the dictionary. Always throws an exception /// indicating that this method is not supported in a read-only dictionary. /// /// Key to add. /// Value to associated with the key. /// Always thrown. void IDictionary.Add(TKey key, TValue value) { MethodModifiesCollection(); } /// /// Removes a key from the dictionary. Always throws an exception /// indicating that this method is not supported in a read-only dictionary. /// /// Key to remove from the dictionary. /// True if the key was found, false otherwise. /// Always thrown. public virtual bool Remove(TKey key) { MethodModifiesCollection(); return false; } /// /// 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); } /// /// 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 /// in 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); /// /// The indexer of the dictionary. The set accessor throws an NotSupportedException /// stating the dictionary is read-only. /// /// The get accessor is implemented by calling TryGetValue. /// Key to find in the dictionary. /// The value associated with the key. /// Always thrown from the set accessor, indicating /// that the dictionary is read only. /// Thrown from the get accessor if the key /// was not found. public virtual TValue this[TKey key] { get { TValue value; bool found = TryGetValue(key, out value); if (found) return value; else throw new KeyNotFoundException(Strings.KeyNotFound); } set { MethodModifiesCollection(); } } /// /// 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(); } #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 /// /// 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; } } #endregion #region IDictionary Members /// /// Adds a key-value pair to the collection. Always throws an exception /// indicating that this method is not supported in a read-only dictionary. /// /// Key to add to the dictionary. /// Value to add to the dictionary. /// Always thrown. void IDictionary.Add(object key, object value) { MethodModifiesCollection(); } /// /// Clears this dictionary. Always throws an exception /// indicating that this method is not supported in a read-only dictionary. /// /// Always thrown. void IDictionary.Clear() { MethodModifiesCollection(); } /// /// 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. Always throws an exception /// indicating that this method is not supported in a read-only dictionary. /// /// The key to remove. /// Always thrown. void IDictionary.Remove(object key) { MethodModifiesCollection(); } /// /// 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. /// /// Always returns true. bool IDictionary.IsFixedSize { get { return true; } } /// /// Returns if this dictionary is read-only. /// /// Always returns true. bool IDictionary.IsReadOnly { get { return true; } } /// /// 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 the value associated with a given key. When getting a value, if this /// key is not found in the collection, then null is returned. If the key is not of the correct type /// for this dictionary, null is returned. /// /// The value associated with the key, or null if the key was not present. /// Always thrown from the set accessor, indicating /// that the dictionary is read only. 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 { MethodModifiesCollection(); } } #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 ReadOnlyDictionaryBase myDictionary; /// /// Constructor. /// /// The dictionary this is associated with. public KeysCollection(ReadOnlyDictionaryBase 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<TKey> and ICollection for the /// Values collection. The collection is read-only. /// [Serializable] private sealed class ValuesCollection : ReadOnlyCollectionBase { private ReadOnlyDictionaryBase myDictionary; public ValuesCollection(ReadOnlyDictionaryBase 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; } } } } }