31

I want to use a custom object as a Dictionary key, mainly, I have something like this: (I can't use .net 4.0 so I don't have tuples)

class Tuple<A, B> : IEquatable<Tuple<A,B>>
{
  public A AValue { get; set; }
  public B BValue { get; set; }

  public Tuple(A a, B b){ AValue = a; BValue = b; }

  public bool Equals(Tuple<A, B> tuple)
  {
    return tuple.AValue.Equals(AValue) && tuple.BValue.Equals(BValue);
  }

  public bool Equals(object o)
  {
  return this.Equals(o as Tuple<A,B>);
  }
}

I then do something like this.

  var boolmap = new Dictionary<Tuple<bool, bool>, string>();
  boolmap.Add(new Tuple<bool,bool>(true, true), "A");
  boolmap.Add(new Tuple<bool,bool>(true, false), "B");
  boolmap.Add(new Tuple<bool,bool>(false, true), "C");
  boolmap.Add(new Tuple<bool,bool>(false, false), "D");
  var str = boolmap[new Tuple<bool,bool>(true, false)];

I get a KeyNotFound exception at the last line. Why is this ? Isn't it enough I implement IEquatable ?

Thanks

rafalio
  • 3,928
  • 4
  • 30
  • 33
  • 3
    where's your get hash code implementation? if your putting a equals you should always have a get hash code – Manatherin Aug 09 '11 at 16:02
  • 8
    Your code gives you a warning, and for a reason. When you fix that warning, you'll get another warning - and when you fix that, your code will work. Never ignore warnings. – Jon Skeet Aug 09 '11 at 16:13
  • You could have seen: [using-an-object-as-a-generic-dictionary-key-in-net](http://stackoverflow.com/questions/634826/using-an-object-as-a-generic-dictionary-key-in-net) – nawfal Jun 10 '14 at 08:45

3 Answers3

45

You also need to override GetHashCode() (and preferably also Equals()). Your otherwise-equal object is returning a different hash code, which means that the key is not found when looked up.

The GetHashCode() contract specifies that the return value from two objects MUST be equal when the two objects are considered equal. This is the root of your problem; your class does not meet this requirement. The contract does not specify that the value must be different if they are not equal, but this will improve performance. (If all of the objects return the same hash code, you may as well use a flat list from a performance perspective.)

A simple implementation in your case might be:

public override int GetHashCode()
{
    return AValue.GetHashCode() ^ BValue.GetHashCode();
}

Note that it might be a good idea to test if AValue or BValue are null. (This will be somewhat complicated since you don't constrain the generic types A and B, so you cannot just compare the values to null -- the types could be value types, for example.)1

It's also a good idea to make classes you intend to use as dictionary keys immutable. If you change the value of an object that is being used as a key, the dictionary will exhibit weird behavior since the object is now in a bucket where it doesn't belong.


1 Note that you could make use of EqualityComparer<A>.Default.GetHashCode(AValue) (and similar for BValue) here, as that will eliminate the need for a null check.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • I would add that there is very important set of MANDATORY rules to keep at in order to let your object behave correctly within dictionary. [Implementin Equals()](http://geekswithblogs.net/gmamaladze/archive/2010/11/27/implementing-the-equals-method-in-.net-and--linear-algebra.aspx) [MSDN on implementing Equals()](http://msdn.microsoft.com/en-us/library/336aedhh.aspx) – George Mamaladze Aug 09 '11 at 16:23
  • 1
    Another excellent reference here: http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx – LukeH Aug 09 '11 at 16:38
8

You need to override GetHashCode when you override Equals method. More explanation can be found here:

Why is it important to override GetHashCode when Equals method is overridden?

Community
  • 1
  • 1
Vivek
  • 16,360
  • 5
  • 30
  • 37
0

I was just overriding the GetHashCode function, but it seems that even though GetHashCode returned same value, the updation didn't happened. Having the Equals as well, everything is fun and frolic

Neeraj Dubey
  • 4,401
  • 8
  • 30
  • 49
  • 2
    When two objects return different hash codes, the system assumes they are different. When they returns the same hash code, the system knows they "might" be equal, so it calls object1.Equals(object2) and concludes they are equal if Equals returns true, while they are different if Equals returns false. If you don't override Equals Object.Equals is called which returns the object's reference, so two objects are considered equal only if they are _the_same_object_. – Fry Simpson Jul 09 '18 at 00:29