Monday, 17 January 2011

IDictionary, injection and covariance

I have a class named Lookup, and want to inject a Dictionary that maps a key to a list of values. Easy enough – just:

public class Lookup<T1, T2>
{
    public Lookup(IDictionary<T1, IList<T2>> underlyingDict)
    {
        // use ctor to inject a dictionary
    }

    public void Add(T1 key, T2 value)
    {
        // add pair to an underlying collection
    }
}

However, this means that I have to give Lookup control over the type of list used. I can't create the mapping I want and then inject it for two reasons:

First, because the IDictionary interface isn't covariant I can’t just create an instance of the closed type that I want – this doesn’t compile:

public class Program()
{
    public static void Main(string[] args)
    {
        IDictionary<string, IList<string>> dict =
            new Dictionary<string, List<string>>();

    	Lookup<string, string> c = new Lookup<string, string>(dict);
    }
}
Note that I can pass in an instance of IDictionary<string, IList<string>>, but then I give Lookup control over the implementation of IList used. When a new key is added to the _underlyingDict a new instance of a class would need to be created – by Lookup.

Second -- even if I could inject as I wanted, Lookup<T1, T2> would need to instantiate a new list for each new key added -- and it doesn't have any information about implementation of IList<T2> I used, so wouldn't know how to create it.

So; to keep control over the implementation used I needed to provide Lookup with some information about the IList implementation I’d chosen. I did it with constrained generics:

public class Lookup<T1, T2, T3> where T2 : IList<T3>, new()
{
    private IDictionary<T1, T2> _underlyingDict;

    public MyLookup(IDictionary<T1, T2> dict)
    {
        _underlyingDict = dict;
    }

    public T2 Get(T1 key)
    {
        return _underlyingDict[key];
    }

    public void Add(T1 key, T3 value)
    {
        if (!_underlyingDict.ContainsKey(key))
        {
            _underlyingDict[key] = new T2();
        }
        _underlyingDict[key].Add(value);
    }
}

Here I've individually specified the key type to use (T1), the List type (T2), and the List item or value type (T3). The constraint T2 : IList<T3> ensures that I’m getting something with the right interface, and the constraint new() is needed as Lookup is going to have to instantiate it. (Without it, the compiler helpfully coughs up: "Cannot create an instance of the variable type 'T2' because it does not have the new() constraint"). To show it in action we can add and retrieve a key-value pair:

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, List<string>> map = 
            new Dictionary<string, List<string>>();

        Lookup<string, List<string>, string> lookup = 
            new Lookup<string, List<string>, string>(map);

        lookup.Add("foo", "bar");
        Console.WriteLine(lookup.Get("foo")[0]);

        Console.ReadKey();
    }
}

It appears to work, but it makes the definition of Lookup rather ugly.

1 comment:

Annie Calvert said...
This comment has been removed by a blog administrator.