The change from Hashtable to Dictionary

As some of you with the Whidbey preview bits have no doubt noticed, we introduced set of generic collection classes in System.Collections.Generic. Far from just making generic versions of the current collections, we took the time to revisit how we really expect people to use these types and, in some cases, learned from our mistakes in V1 of the framework.

One of the new collections is Dictionary<K, V>. The big change that we made from Hashtable has caused a fair amount of internal debate so I thought it would be interesting to see what actual, real-live, customers think of the issue. In brief, with Hashtable’s indexer, if the key is not found null is returned, with Dictionary<K,V>’s indexer an exception is thrown.

For example:

            Hashtable hashtable = new Hashtable();

            hashtable.Add("key1", "data1");

            hashtable.Add("key2", "data2");

            Console.WriteLine(hashtable["key1"]); //displays "data1"

            Console.WriteLine(hashtable["key42"]==null); //displays "true"

            Dictionary<string, string> dic = new Dictionary<string, string>();

            dic.Add("key1", "data1");

            dic.Add("key2", "data2");

            Console.WriteLine(dic["key1"]); //displays "data1"

            Console.WriteLine(dic["key42"]); //throws a KeyNotFoundException

The change has already caused me pain in porting some code that uses Hashtable over to Dictionary<K, V> and some have argued that this use of exceptions is overkill. The primary reason Dictionary<K, V> throws is that there is no “error” value that works over any V. Hashtable is able to return null because the key is always a reference type (even if you try to make the key be an int (for example) it will be boxed before added to the hashtable). If V happens to be a value type then “null” is not a valid return value. We do have Nullable<T:ValueType> but that is a topic for another day.

All types in the CLR do have a notion of their “default value”. In C# 2.0 you access this value by using the default operator. The rules for default() are pretty easy: null for reference types and zero for value types ( 0, 0.0, false, etc).

default(object) ==null

default(int) ==0

default(bool) ==false

default(string) ==null

So we could have Dictionary<K, V> return default(V) if the key does not exists. We have proven with Hashtable that this is a pretty workable plan when V is a reference type. However, it is arguably a little misleading with V is a valuetype. After all zero is often in the valid range for int values. I should also mention that you can always use Dictionary<K,Nullable<V>> if default(V) isn't an appropriate marker value. For example, with Dictionary<string,Nullable<int>> you will indeed get a null if an entry isn't found.

So this really comes down the question of how commonly do we expect V to be a value type times how bad using the default(V) as a flag verses how bad it is to throw the KeyNotFoundException as described above.

Questions for you:

  1. How commonly do you use a value type for V?
  2. In those cases will default(V) or the Nullable designs address the issue?
  3. Do you like the idea of the indexer throwing a KeyNotFoundException?

We would love to have your feedback!