IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Hashtables and Dictionaries and Nulls, Oh My!

Tuesday 27 April, 2004, 01:22 PM

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:

  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?

My answers are:

  1. Rarely, but mainly because I'm concerned about boxing overheads. I anticipate using values more in Whidbey.
  2. I suspect in these cases I would prefer an exception to a default value most times.
  3. I have mixed feelings here - I have written a fair amount of code that depends on the current behaviour, but in the new scenarios where I expect to use value types, I can see how an exception might be better.

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.)

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk