Dictionary with null key? [duplicate]

Firstly, why doesn't Dictionary<TKey, TValue> support a single null key?

Secondly, is there an existing dictionary-like collection that does?

I want to store an "empty" or "missing" or "default" System.Type, thought null would work well for this.


More specifically, I've written this class:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        var type = obj.GetType();

        foreach (var entry in cases)
        {
            if (entry.Key == null || type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                break;
            }
        }
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action());
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action<T> action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action((T)x));
    }

    public static KeyValuePair<Type, Action<object>> Default(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(null, x => action());
    }
}

For switching on types. There are two ways to use it:

  1. Statically. Just call Switch.Execute(yourObject, Switch.Case<YourType>(x => x.Action()))
  2. Precompiled. Create a switch, and then use it later with switchInstance.Execute(yourObject)

Works great except when you try to add a default case to the "precompiled" version (null argument exception).


1) Why: As described before, the problem is that Dictionary requires an implementation of the Object.GetHashCode() method. null does not have an implementation, therefore no hash code associated.

2) Solution: I have used a solution similar to a NullObject pattern using generics that enables you to use the dictionary seamlessly (no need for a different dictionary implementation).

You can will use it, like this:

var dict = new Dictionary<NullObject<Type>, string>();
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";
dict[null] = "null type";

Assert.AreEqual("int type", dict[typeof(int)]);
Assert.AreEqual("string type", dict[typeof(string)]);
Assert.AreEqual("null type", dict[null]);

You just need to create this struct once in a lifetime :

public struct NullObject<T>
{
    [DefaultValue(true)]
    private bool isnull;// default property initializers are not supported for structs

    private NullObject(T item, bool isnull) : this()
    {
        this.isnull = isnull;
        this.Item = item;
    }

    public NullObject(T item) : this(item, item == null)
    {
    }

    public static NullObject<T> Null()
    {
        return new NullObject<T>();
    }

    public T Item { get; private set; }

    public bool IsNull()
    {
        return this.isnull;
    }

    public static implicit operator T(NullObject<T> nullObject)
    {
        return nullObject.Item;
    }

    public static implicit operator NullObject<T>(T item)
    {
        return new NullObject<T>(item);
    }

    public override string ToString()
    {
        return (Item != null) ? Item.ToString() : "NULL";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return this.IsNull();

        if (!(obj is NullObject<T>))
            return false;

        var no = (NullObject<T>)obj;

        if (this.IsNull())
            return no.IsNull();

        if (no.IsNull())
            return false;

        return this.Item.Equals(no.Item);
    }

    public override int GetHashCode()
    {
        if (this.isnull)
            return 0;

        var result = Item.GetHashCode();

        if (result >= 0)
            result++;

        return result;
    }
}

It just hit me that your best answer is probably to just keep track of whether a default case has been defined:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;
    private Action<object> defaultCase;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            if (entry.Key == null)
                defaultCase = entry.Value;
            else
                _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
        else if (defaultCase != null)
            defaultCase(obj);
    }

...

The whole rest of your class would remain untouched.