158

I know there isn't one in the BCL but can anyone point me to a good opensource one?

By Multi I mean 2 keys. ;-)

Jegadeesh
  • 335
  • 2
  • 16
SuperSuperDev1234
  • 4,765
  • 5
  • 26
  • 19
  • 3
    Do you want a key that is made of multiple attributes or do you want it possible so that the same key can exist more than once in the same dictionary? These are different. – DigitalZebra Jul 23 '09 at 13:50
  • 2
    http://stackoverflow.com/questions/689940/hashtable-with-multidimensional-key-in-c – Paul Ruane Jul 23 '09 at 13:54
  • You might want to add an example usage, to clarify what you mean. – Michael Donohue Jul 23 '09 at 14:21
  • 2
    These are worth a look: http://noocyte.wordpress.com/2008/02/18/double-key-dictionary/ and http://www.codeproject.com/Articles/32894/C-Multi-key-Generic-Dictionary – JamieSee May 29 '12 at 18:18
  • 3
    no +1 for making the question this short and vague. Your question can be implied in 10 different ways – nawfal Mar 28 '13 at 10:21
  • I wrote an answer for C# 7.0 here: https://stackoverflow.com/a/46829428/531524 – Huseyin Yagli Oct 19 '17 at 12:14
  • How is this question marked duplicate? The question referenced is for a door with multiple keyholes, while this question could just as well be a door with one keyhole and multiple keys – Kresten Oct 23 '20 at 14:13
  • I wrote a [MultiKeyDictionary](https://www.nuget.org/packages/MultiKeyCollections/) package for net472, net481, netstandard2.1, and net6.0. – Andreas Jan 31 '23 at 09:05

16 Answers16

76

I've also used tuples as jason in his answer does. However, I suggest you simply define a tuple as a struct:

public struct Tuple<T1, T2> {
    public readonly T1 Item1;
    public readonly T2 Item2;
    public Tuple(T1 item1, T2 item2) { Item1 = item1; Item2 = item2;} 
}

public static class Tuple { // for type-inference goodness.
    public static Tuple<T1,T2> Create<T1,T2>(T1 item1, T2 item2) { 
        return new Tuple<T1,T2>(item1, item2); 
    }
}

You get immutability, .GetHashcode and .Equals for free, which (while you're waiting for C# 4.0) is nice 'n simple...

One warning however: the default GetHashcode implementation (sometimes) only considers the first field so make sure to make the first field the most discriminating or implement GetHashcode yourself (e.g. using FieldwiseHasher.Hash(this) from ValueUtils), otherwise you'll likely run into scalability issues.

Also, you get to avoid nulls which tend to complicate matters (and if you really want nulls, you just make your Tuple<> nullable). Slightly offtopic, am I the only one annoyed at the framework-level lack of support for non-null references? I work on large project, and occasionally a null creeps in somewhere it really shouldn't -- and hey presto, you get a nullreference exception -- but with a stack trace that points you to the reference's first usage, not the actually faulty code.

Of course, .NET 4.0 is pretty old by now; most of us can just use .NET 4.0's tuple.

Edit: to workaround the poor GetHashCode implementation that .NET provides for structs I've written ValueUtils, which also allows you to use real names for your multi-field keys; that means you might write something like:

sealed class MyValueObject : ValueObject<MyValueObject> {
    public DayOfWeek day;
    public string NamedPart;
    //properties work fine too
}

...which hopefully makes it easier to have human-readable names for data with value semantics, at least until some future version of C# implements proper tuples with named members; hopefully with decent hashcodes ;-).

Martin Schneider
  • 14,263
  • 7
  • 55
  • 58
Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
  • 2
    Nice blurb on reference versus value-type decision for tuples in .NET: http://msdn.microsoft.com/en-us/magazine/dd942829.aspx#id0400060 – jason Jul 23 '09 at 14:03
  • Indeed (after reading the article), if you use the default struct-implied .Equals and .GetHashcode implementation, a tuple with a double that's NaN will equal itself, even though double.NaN != double.NaN... I can live with that. – Eamon Nerbonne Jul 23 '09 at 14:18
  • That article says they used a reference type since didn't like causing confusing due to inconsistency of semantics between struct/reference types - but I don't see any argument as to why a reference type would thus be preferable; simply that you'd like all tuples to be of the same type. – Eamon Nerbonne Jul 23 '09 at 14:23
  • You're not the only one that's annoyed by that. I read an article where one of the creators of C# and .NET said that support for non-null references was a feature that he really would have liked to included, but they (like the rest of us) were up against a deadline and couldn't fit that in, and now that the framework is in common use, I imagine it would be difficult to add support for that after-the-fact. – StriplingWarrior Dec 17 '09 at 17:26
  • C# designers have already said they regret for the lack of support for non-nulls. Most probably in a new-born C# – Luis Filipe Feb 20 '13 at 18:29
  • This advice should be **heavily tempered** - the default implementation of [ValueType.GetHashCode](http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx) can yield many collisions, which can lead to terrible Dictionary performance. It even says so: "If you call the derived type's GetHashCode method, the return value is not likely to be suitable for use as a key in a hash table". There's a great treatment of the subject by Eric Lippert of Microsoft [here](http://stackoverflow.com/questions/3841602/why-is-valuetype-gethashcode-implemented-like-it-is) – bacar Aug 06 '13 at 00:02
  • @bacar: that's a *very* good point, and one I've run into myself: I've included that warning in the answer. – Eamon Nerbonne Aug 06 '13 at 09:45
  • @Eamon - It's actually worse than "only considers the first field" - sometimes it appears to consider *no* fields (despite what the documentation says)! See Jon Skeet's analysis/answer to [this question](http://stackoverflow.com/questions/12657348/new-keyvaluepairuint32-uint32i-j-gethashcode-high-rate-of-duplicates). He tests `KeyValuePair` (an example of a struct that doesn't override GetHashCode) and finds that it _appears_ to always return the same hash! – bacar Aug 06 '13 at 11:30
  • I don't think this answers the question at all - having a key composed of two items is not the same as having two indices – Dave Hillier Oct 08 '13 at 12:35
  • The question makes not mention of two indices - what are you referring to? – Eamon Nerbonne Oct 08 '13 at 13:06
  • Getting `GetHashcode` and `Equals` for free is simple, but not nice. It comes with a performance penalty as internally the value equality is done using reflection. If it is a struct, one better implement `IEquatable`. – nawfal May 19 '14 at 13:21
  • @nawfal I've got an implementation of an expression-tree based implementation that efficiently and easily implements those for you, I'll have a look if I can get the OK to open source that. – Eamon Nerbonne May 20 '14 at 13:33
  • @EamonNerbonne WoW, that will be great! Let me know if its written somewhere. I will try writing one myself too! :) – nawfal May 20 '14 at 14:13
  • 1
    @nawfal: I decided to reimplement the hashcode function generator. It works; but it's still pretty barebones: https://github.com/EamonNerbonne/ValueUtils – Eamon Nerbonne May 27 '14 at 08:49
  • 1
    @EamonNerbonne looks good. I'm not very sure about `OverriddenHashCodeMethod`'s job. Will have to test. – nawfal May 27 '14 at 09:09
  • 1
    @nawfal: I'll polish it up and add more features, and release it to nuget. If you actually intend to use this, I'd love feedback on how to make it work for your usecase - feel free to comment on github! – Eamon Nerbonne May 27 '14 at 10:01
  • 1
    @nawfal: OK, I think it's in a pretty usable state now. I'd be interested in your opinion on two specific design issues: https://github.com/EamonNerbonne/ValueUtils/issues/1 and https://github.com/EamonNerbonne/ValueUtils/issues/2. Even so, this is ready for real use - just try refering to nuget:ValueUtils, and create your class ala `sealed class MyClass : ValueObject`! Early perf testing shows it to be much faster than ValueType, considerably faster than `Tuple<>`, and within a factor 2 of hand-rolled code. – Eamon Nerbonne May 29 '14 at 19:01
  • @EamonNerbonne looks good, though using inheritance as code sharing mechanism is not all that good. I will inspect it in detail in sometime. May be a month :) Quite busy for this month. Nevertheless, thanks! – nawfal May 29 '14 at 19:15
60

I use a Tuple as the keys in a Dictionary.

public class Tuple<T1, T2> {
    public T1 Item1 { get; private set; }
    public T2 Item2 { get; private set; }

    // implementation details
}

Be sure to override Equals and GetHashCode and define operator!= and operator== as appropriate. You can expand the Tuple to hold more items as needed. .NET 4.0 will include a built-in Tuple.

jason
  • 236,483
  • 35
  • 423
  • 525
  • 4
    Check out this blog post on performance tests between multiple "multi-key" dictionary implementations [here](http://www.fyslexicduck.com/2011/05/performance-tests-between-multiple.html) – Aron Apr 22 '13 at 21:13
  • 1
    @AronW The blog post never uses a `Tuple` as a key as far as I can tell? – flindeberg Oct 30 '15 at 15:54
  • If you're going to write your own Tuple class, may as well use named items instead of T1, T2... – Denise Skidmore Apr 25 '18 at 20:38
38

Tuples will be (are) in .Net 4.0 Until then, you can also use a

 Dictionary<key1, Dictionary<key2, TypeObject>> 

or, creating a custom collection class to represent this...

 public class TwoKeyDictionary<K1, K2, T>: 
        Dictionary<K1, Dictionary<K2, T>> { }

or, with three keys...

public class ThreeKeyDictionary<K1, K2, K3, T> :
    Dictionary<K1, Dictionary<K2, Dictionary<K3, T>>> { }
Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • 1
    Brilliant answer -- this worked perfectly for my needs. – HanClinto Dec 06 '12 at 07:04
  • 4
    This is not multi-key dictionary, this is quite bad implementation of a tupled-key dictionary, because you can't access values by second key, neither you can access them without it (unless you get it by Values of inner dictionary, but it can contain multiple items). Also, your solution lacks code for adding and removing items. – Aberro Apr 09 '18 at 14:24
  • 2
    jeeez... I'm not even sure what you are talking about. You want a multi-keyed dictionary to be able to access objects with *either* of the keys? That is in no way equivalent to a tuple as requested by the op. To your second, you really expect a fully fleshed out professional implementation, with all the bells and whistles, as an answer to the question? Dream on, I get paid for that. Write your own code. – Charles Bretana Dec 21 '21 at 23:04
25

Many good solutions here, What I am missing here is an implementation based on the build in Tuple type, so I wrote one myself.

Since it just inherits from Dictionary<Tuple<T1,T2>, T> you can always use both ways.

var dict = new Dictionary<int, int, Row>();
var row = new Row();
dict.Add(1, 2, row);
dict.Add(Tuple.Create(1, 2, row));
dict.Add(new Tuple<int, int>(1, 2));

here is the code.

public class Dictionary<TKey1,TKey2,TValue> :  Dictionary<Tuple<TKey1, TKey2>, TValue>, IDictionary<Tuple<TKey1, TKey2>, TValue>
{

    public TValue this[TKey1 key1, TKey2 key2]
    {
        get { return base[Tuple.Create(key1, key2)]; }
        set { base[Tuple.Create(key1, key2)] = value; }
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        base.Add(Tuple.Create(key1, key2), value);
    }

    public bool ContainsKey(TKey1 key1, TKey2 key2)
    {
        return base.ContainsKey(Tuple.Create(key1, key2));
    }
}

Please be aware that this implementation depends on the Tuple.Equals() implementation itself:

http://msdn.microsoft.com/en-us/library/dd270346(v=vs.110).aspx

The obj parameter is considered to be equal to the current instance under the following conditions:

  • It is a Tuple object.
  • Its two components are of the same types as the current instance.
  • Its two components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component.
56ka
  • 1,463
  • 1
  • 21
  • 37
Jürgen Steinblock
  • 30,746
  • 24
  • 119
  • 189
14

I wrote and have used this with success.

public class MultiKeyDictionary<K1, K2, V> : Dictionary<K1, Dictionary<K2, V>>  {

    public V this[K1 key1, K2 key2] {
        get {
            if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
                throw new ArgumentOutOfRangeException();
            return base[key1][key2];
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new Dictionary<K2, V>();
            this[key1][key2] = value;
        }
    }

    public void Add(K1 key1, K2 key2, V value) {
            if (!ContainsKey(key1))
                this[key1] = new Dictionary<K2, V>();
            this[key1][key2] = value;
    }

    public bool ContainsKey(K1 key1, K2 key2) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2);
    }

    public new IEnumerable<V> Values {
        get {
            return from baseDict in base.Values
                   from baseKey in baseDict.Keys
                   select baseDict[baseKey];
        }
    } 

}


public class MultiKeyDictionary<K1, K2, K3, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, V>> {
    public V this[K1 key1, K2 key2, K3 key3] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, V>();
            this[key1][key2, key3] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, V>();
            this[key1][key2, key3, key4] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, V>();
            this[key1][key2, key3, key4, key5] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, V>();
            this[key1][key2, key3, key4, key5, key6] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>();
            this[key1][key2, key3, key4, key5, key6, key7] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10, K11 key11] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10, K11 key11) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10, key11);
    }
}
Herman Schoenfeld
  • 8,464
  • 4
  • 38
  • 49
9

I frequently use this because it's short and provides the syntactic sugar I need...

public class MultiKeyDictionary<T1, T2, T3> : Dictionary<T1, Dictionary<T2, T3>>
{
    new public Dictionary<T2, T3> this[T1 key]
    {
        get
        {
            if (!ContainsKey(key))
                Add(key, new Dictionary<T2, T3>());

            Dictionary<T2, T3> returnObj;
            TryGetValue(key, out returnObj);

            return returnObj;
        }
    }
}

To use it:

dict[cat][fish] = 9000;

where the "Cat" key doesn't have to exist either.

max
  • 716
  • 7
  • 22
  • I should also mention that it's arbitrary to create further nestings with another class : Dictionary>, etc. Also, I avoid using it, but it cleans up funky nested dictionaries pretty well. – max Dec 17 '12 at 18:54
  • What's a good way to check if the [cat][fish] or [cat][mouse] keys exist? – goku_da_master Dec 18 '12 at 00:54
  • @goku_da_master `dict[cat].ContainsKey(mouse)` where if cat doesn't exist, you always get false, because it is newed up then. If you're using it this way a lot I would benchmark it though--it's not going to be a very optimized way of doing this. – max Dec 18 '12 at 18:02
  • 1
    Underrated solution! (: Very handy when using .NET earlier than 4.0. – Sebastian Werk Mar 06 '20 at 11:38
7

I'm currently simply concatenating the keys into a single string as a workaround. Of course, this will not work on non-string keys. Would love to know the answer as well.

Adrian Godong
  • 8,802
  • 8
  • 40
  • 62
  • Why not .ToString() the non-string keys? – n8wrl Jul 23 '09 at 13:45
  • 3
    Because on most class .ToSting() will be the same for all values (ie. the types name). Not all types have a valid string representation, etc, etc. – Matthew Scharley Jul 23 '09 at 13:47
  • Yes, but you can usually override ToString in those classes. – Badaro Jul 23 '09 at 13:52
  • 1
    Then, one cannot use sealed classes as keys, and instead of implementing one class which is a tuple of many, one must subclass many classes. – maxwellb Jul 23 '09 at 13:55
  • 1
    Yeah, too many BCL classes don't implement ToString, which means you can't assume ToString will return a unique representation of a given type's state. TL;DR: fail –  Jul 23 '09 at 13:57
  • 5
    Using string concatenation can be dangerous. For example, if you have ints 123 and 456, and concatenate them to create a key, this would give "123456". If you have 12 and 3456 and you concatenate them, you get... "123456". Oops. – Meta-Knight Jul 23 '09 at 14:07
  • 11
    @Meta-Knight Use separator. Duh. – Adrian Godong Jul 23 '09 at 15:46
  • @AdrianGodong - please improve this answer by giving a simple example. Such as `dict[person.First + "-" + person.Last + "-" + person.DOB]` Then I think this would receive more upvotes. It really is the most straight forward way. – Don Cheadle Aug 13 '18 at 00:22
6

Take a look at Wintellect's PowerCollections (CodePlex download). I think their MultiDictionary does something like that.

It's a dictionary of dictionaries, so you have 2 keys to access each object, the key for the main dictionary to get you the required sub dictionary, and then the second key for the sub dictionary to get you the required item. Is that what you mean?

Simon P Stevens
  • 27,303
  • 5
  • 81
  • 107
5

Is there anything wrong with

new Dictionary<KeyValuePair<object, object>, object>
?
roko-koko
  • 3
  • 3
JSBձոգչ
  • 40,684
  • 18
  • 101
  • 169
  • 3
    Is there a built-in Pair class? – Michael Donohue Jul 23 '09 at 13:53
  • @MichaelDonohue: As of .NET 4.0, there is the [`Tuple`](http://msdn.microsoft.com/en-us/library/system.tuple.aspx) class. – Matthew May 20 '13 at 18:20
  • 4
    Yes, there is something wrong: KVP uses [ValueType.GetHashCode](http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx) - "If you call the derived type's GetHashCode method, the return value is not likely to be suitable for use as a key in a hash table." KVP is a completely inappropriate choice for a Dictionary key and could yield many collisions. – bacar Aug 05 '13 at 23:13
5

I've googled for this one: http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx. I guess it's main feature compared to using struct to contain 2 keys in regular dictionary is that you can later reference by one of the keys, instead of having to supply 2 keys.

Marcin Deptuła
  • 11,789
  • 2
  • 33
  • 41
3

Could you use a Dictionary<TKey1,Dictionary<TKey2,TValue>>?

You could even subclass this:

public class DualKeyDictionary<TKey1,TKey2,TValue> : Dictionary<TKey1,Dictionary<TKey2,TValue>>

EDIT: This is now a duplicate answer. It also is limited in its practicality. While it does "work" and provide ability to code dict[key1][key2], there are lots of "workarounds" to get it to "just work".

HOWEVER: Just for kicks, one could implement Dictionary nonetheless, but at this point it gets a little verbose:

public class DualKeyDictionary<TKey1, TKey2, TValue> : Dictionary<TKey1, Dictionary<TKey2, TValue>> , IDictionary< object[], TValue >
{
    #region IDictionary<object[],TValue> Members

    void IDictionary<object[], TValue>.Add( object[] key, TValue value )
    {
        if ( key == null || key.Length != 2 )
            throw new ArgumentException( "Invalid Key" );

        TKey1 key1 = key[0] as TKey1;
        TKey2 key2 = key[1] as TKey2;

        if ( !ContainsKey( key1 ) )
            Add( key1, new Dictionary<TKey2, TValue>() );

        this[key1][key2] = value;
    }

    bool IDictionary<object[], TValue>.ContainsKey( object[] key )
    {
        if ( key == null || key.Length != 2 )
            throw new ArgumentException( "Invalid Key" );

        TKey1 key1 = key[0] as TKey1;
        TKey2 key2 = key[1] as TKey2;

        if ( !ContainsKey( key1 ) )
            return false;

        if ( !this[key1].ContainsKey( key2 ) )
            return false;

        return true;
    }
maxwellb
  • 13,366
  • 2
  • 25
  • 35
  • 1
    I see, Charles is thinking the same thing. The problem with these is, however, in the allocation. It might be a beast to manage. – maxwellb Jul 23 '09 at 13:52
  • +1 for the naming, DualKey.. Have been looking for a good name :) – nawfal Mar 30 '13 at 08:28
3

Here's a fleshed out example of a pair class which can be used as the key to a Dictionary.

public class Pair<T1, T2>
{
    public T1 Left { get; private set; }
    public T2 Right { get; private set; }

    public Pair(T1 t1, T2 t2)
    {
        Left = t1;
        Right = t2;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof(Pair<T1, T2>)) return false;
        return Equals((Pair<T1, T2>)obj);
    }

    public bool Equals(Pair<T1, T2> obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        return Equals(obj.Left, Left) && Equals(obj.Right, Right);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (Left.GetHashCode() * 397) ^ Right.GetHashCode();
        }
    }
}
Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Michael Donohue
  • 11,776
  • 5
  • 31
  • 44
3

If anyone is looking for a ToMultiKeyDictionary() here is an implementation that should work with most of the answers here (based on Herman's):

public static class Extensions_MultiKeyDictionary {

    public static MultiKeyDictionary<K1, K2, V> ToMultiKeyDictionary<S, K1, K2, V>(this IEnumerable<S> items, Func<S, K1> key1, Func<S, K2> key2, Func<S, V> value) {
        var dict = new MultiKeyDictionary<K1, K2, V>(); 
        foreach (S i in items) { 
            dict.Add(key1(i), key2(i), value(i)); 
        } 
        return dict; 
    }

    public static MultiKeyDictionary<K1, K2, K3, V> ToMultiKeyDictionary<S, K1, K2, K3, V>(this IEnumerable<S> items, Func<S, K1> key1, Func<S, K2> key2, Func<S, K3> key3, Func<S, V> value) {
        var dict = new MultiKeyDictionary<K1, K2, K3, V>(); 
        foreach (S i in items) { 
            dict.Add(key1(i), key2(i), key3(i), value(i)); 
        } 
        return dict; 
    }
}
katbyte
  • 2,665
  • 2
  • 28
  • 19
2

I think you would need a Tuple2 like class. Be sure that it's GetHashCode() and Equals() is based upon the two contained elements.

See Tuples in C#

Community
  • 1
  • 1
Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
2

Here's my implementation. I wanted something to hide the implementation of the Tuple concept.

  public class TwoKeyDictionary<TKey1, TKey2, TValue> : Dictionary<TwoKey<TKey1, TKey2>, TValue>
  {
    public static TwoKey<TKey1, TKey2> Key(TKey1 key1, TKey2 key2)
    {
      return new TwoKey<TKey1, TKey2>(key1, key2);
    }

    public TValue this[TKey1 key1, TKey2 key2]
    {
      get { return this[Key(key1, key2)]; }
      set { this[Key(key1, key2)] = value; }
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
      Add(Key(key1, key2), value);
    }

    public bool ContainsKey(TKey1 key1, TKey2 key2)
    {
      return ContainsKey(Key(key1, key2));
    }
  }

  public class TwoKey<TKey1, TKey2> : Tuple<TKey1, TKey2>
  {
    public TwoKey(TKey1 item1, TKey2 item2) : base(item1, item2) { }

    public override string ToString()
    {
      return string.Format("({0},{1})", Item1, Item2);
    }
  }

It helps keeps the usage looking like a Dictionary

item.Add(1, "D", 5.6);

value = item[1, "D"];
Eric
  • 2,207
  • 2
  • 16
  • 16
1

Here's another example using the Tuple class with the Dictionary.

        // Setup Dictionary
    Dictionary<Tuple<string, string>, string> testDictionary = new Dictionary<Tuple<string, string>, string>
    {
        {new Tuple<string, string>("key1","key2"), "value1"},
        {new Tuple<string, string>("key1","key3"), "value2"},
        {new Tuple<string, string>("key2","key3"), "value3"}
    };
    //Query Dictionary
    public string FindValue(string stuff1, string stuff2)
    {
        return testDictionary[Tuple.Create(stuff1, stuff2)];
    }
Justin
  • 414
  • 4
  • 8