(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
Brad Abrams recently posted
an entry on the change
from Hasthtable
to Dictionary
in Whidbey. He points out that while today's
Hashtable
returns null
if you pass an unrecognized key to its indexer, the new generic
Dictionary
class in Whidbey throws an exception instead. (At least that's what it does in the current
Whidbey build.)
Brad asks the following questions:
- How commonly do you use a value type for V?
- In those cases will default(V) or the Nullable designs address the issue?
- Do you like the idea of the indexer throwing a KeyNotFoundException?
My answers are:
But it occurs to me that we can get the best of both worlds. I threw together the following class which wraps
the generic Dictionary
(or indeed any implementation of the generic IDictionary
interface)
and returns default values when unrecognized keys are passed into an indexer. This enables us to choose which
semantics we want. So it doesn't greatly matter what the built-in Dictionary
does, since we can write
an adapter like this to change it. (If the Dictionary
returned default values, we could modify this
wrapper to throw exceptions instead.)
using System; using System.Collections.Generic; // Provides a generic dictionary, but using the old Hashtable // semantics from the indexer. // // The non-generic Hashtable's indexer returns null if we pass // in an unrecognized key. The generic Dictionary throws an // exception instead. This class wraps a Dictionary and reverts // to the old behaviour, returning the default value when an // unrecognized key is passed in. For reference types, the // default value is null, for value types, it will be whatever // the value type's default value is. (Typically zero.) public class Hashtable<K, V> : IDictionary<K, V> { // Underlying dictionary - everything delegates // to this. private IDictionary<K, V> dict; // We provide all the same constructors that the generic // Dictionary provides. public Hashtable() { dict = new Dictionary<K, V>(); } public Hashtable(IComparer<K> comparer) { dict = new Dictionary<K, V>(comparer); } public Hashtable(int capacity) { dict = new Dictionary<K, V>(capacity); } public Hashtable(int capacity, IComparer<K> comparer) { dict = new Dictionary<K, V>(capacity, comparer); } public Hashtable(IDictionary<K, V> originalDictionary) : this(originalDictionary, false) { } public Hashtable(IDictionary<K, V> originalDictionary, IComparer<K> comparer) { dict = new Dictionary<K, V>(originalDictionary, comparer); } // This methods allow an existing dictionary to be wrapped. // (The public constructors that take an IDictionary all // copy the data.) public static Hashtable<K, V> Wrap(IDictionary<K, V> originalDictionary) { return new Hashtable<K, V>(originalDictionary, true); } // Private constructor used to enable a dictionary either to be // copied, or used as the underying storage. private Hashtable(IDictionary<K, V> originalDictionary, bool wrap) { if (wrap) { dict = originalDictionary; } else { dict = new Dictionary<K, V>(originalDictionary); } } // This is the raison d'etre of this whole class - an indexer // which returns null (or 0 or false, or whatever) when a key // is not recognized. public V this[K key] { get { if (!dict.ContainsKey(key)) { // Brad Abrams says that we use the // default operator as: default(type) // but the compiler doesn't like that. // It's happy with this though: return V.default; } return dict[key]; } set { dict[key] = value; } } // Delegating implementations of all other methods. public bool Remove(K key) { return dict.Remove(key); } public void Add(K key, V value) { dict.Add(key, value); } public ICollection<K> Keys { get { return dict.Keys; } } public ICollection<V> Values { get { return dict.Values; } } public bool ContainsKey(K key) { return dict.ContainsKey(key); } public bool IsReadOnly { get { return dict.IsReadOnly; } } public bool Contains(KeyValuePair<K, V> item) { return dict.Contains(item); } public bool Remove(KeyValuePair<K, V> item) { return dict.Remove(item); } public void Clear() { dict.Clear(); } public int Count { get { return dict.Count; } } public int Add(KeyValuePair<K, V> item) { return dict.Add(item); } public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) { dict.CopyTo(array, arrayIndex); } public IEnumerator<KeyValuePair<K, V>> GetEnumerator() { return dict.GetEnumerator(); } }
While writing this, I briefly found myself feeling some sympathy for those who get outraged when a class does something that prevents the use of inheritance. And then, as usually happens under these circumstances, I realised that inheritance was the wrong solution anyway...
What I initially wanted to do was derive from Dictionary
and override the indexer. But you can't do
that because it's not overridable. However, I then realised that this would be a poor solution anyway - by using a
delegation approach here, I've ended up with something much more flexible: an adapter that can wrap any
implementation of IDictionary
, rather than just the built-in Dictionary
class. However,
this did make me feel, and not for the first time, that it would be very useful to have some kind of support for
automating tedious delegation tasks like this. (I think that would be a much more useful facility than, say, supporting
multiple inheritance, which some people seem to want.)