ashes999
ashes999

Reputation: 10163

Dictionary with Arbitrary Tuple for Key (Without Wrapping in a Class)

In C#, the Dictionary class takes a single key and maps it to a single value. I'm looking for something similar, where I can pass in an ordered tuple and get a single value -- without wrapping it in a class.

Here's a pretty hypothetical example of what I want (with buttons, instead of some 2D map tile). Currently, I can do this:

Dictionary<int, Button> buttons = new Dictionary<int, Button>();

If I want to use each button's coordinates as the key, I can do this:

Dictionary<Point, Button> buttons = new Dictionary<Point, Button>();
buttons[new Point(b.X, b.Y)] = b;

What I would like to do is this

Dictionary<int, int, Button> buttons = new Dictionary<int, int, Button>();
buttons[b.X, b.Y] = b;

Again, it's a trivial case with a known work-around. But I just find it annoying that I have to create a new placeholder class (struct?) with each set of parameters that I want to use as a key.

Is this somehow possible?

Upvotes: 1

Views: 3729

Answers (3)

Enigmativity
Enigmativity

Reputation: 117029

This is a situation that extension methods may help.

I've written a bunch of dictionary extension methods to allow functions like GetOrAdd and GetOrDefault that work on standard dictionaries and these help reduce all of the noise in checking if an item already exists in dictionary.

Likewise, you can extend a nested dictionary with extension methods.

So, perhaps you could write code like this:

var buttons = new Dictionary<int, Dictionary<int, Button>>();

buttons.Add(b.X, b.Y, b);
buttons.AddOrReplace(b1.X, b1.Y, b1);

My Add method looks like this:

    public static void Add<K1, K2, V>(
            this IDictionary<K1, Dictionary<K2, V>> @this,
            K1 key1,
            K2 key2,
            V value)
    {
        if (@this == null) { throw new ArgumentNullException("@this"); }
        if (key1 == null) { throw new ArgumentNullException("key1"); }
        if (key2 == null) { throw new ArgumentNullException("key2"); }
        if ([email protected](key1))
        {
            @this[key1] = new Dictionary<K2, V>();
        }
        @this[key1][key2] = value;
    }

This kind of approach is nice because you are still working with standard .NET dictionaries, but getting the functionality you're after with only a relatively minor syntax difference.

An alternative approach is to define your own dictionary-like class. Something like this:

public class TupleDictionary<K1, K2, V>
{
    public void Add(K1 key1, K2 key2, V value);
    public bool ContainsKey(K1 key1, K2 key2);
    public bool Remove(K1 key1, K2 key2);

    public Dictionary<K2, V> this[K1 key1] { get; }
}

And then, because the this indexed property would "get or add" the inner dictionaries, you can write your code like this:

var td = new TupleDictionary<int, int, Button>();

td[b.X][b.Y] = b;

Syntactically it looks the same as your example code, but you'd need to write a fair amount of code just to get this specific syntax.

I think the extension method approach using nested dictionaries is the way to go.

Upvotes: 2

Jay
Jay

Reputation: 57899

public class TupleDictionary<T1, T2, TValue> : Dictionary<Tuple<T1,T2>,TValue>
{
    public TValue this[T1 t1, T2 t2]
    {
        get { return this[new Tuple<T1, T2>(t1, t2)]; }
        set { this[new Tuple<T1, T2>(t1,t2)] = value; }
    }

    public void Add(T1 t1, T2 t2, TValue value)
    {
        Add(new Tuple<T1, T2>(t1, t2), value);
    }

    public void Remove(T1 t1, T2 t2)
    {
        Remove(new Tuple<T1, T2>(t1, t2));
    }

    public bool ContainsKey(T1 t1, T2 t2)
    {
        return ContainsKey(new Tuple<T1, T2>(t1, t2));
    }

    public bool TryGetValue(T1 t1, T2 t2, out TValue value)
    {
        return TryGetValue(new Tuple<T1, T2>(t1, t2), out value);
    }
}

Upvotes: 5

Laurion Burchall
Laurion Burchall

Reputation: 2853

In .Net 4 you can use a tuple as the dictionary key

Dictionary<Tuple<int, int>, Button> buttons = new Dictionary<Tuple<int, int>, Button>();
buttons[new Tuple<int,int>(b.X, b.Y)] = b;

Upvotes: 2

Related Questions