views:

1791

answers:

4

If I want to use objects as the keys for a Dictionary, what methods will I need to override to make them compare in a specific way?

Say I have a a class which has properties:

class Foo {
    public string Name { get; set;}
    public int FooID {get; set;}

   ...
}

And I want to create a:

Dictionary<Foo, List<Stuff>>

I want Foo objects with the same FooID to be considered the same group. Which methods will I need to override in the Foo class?

To summarize: I want to categorize Stuff objects into lists, grouped by Foo objects. (Stuff objects will have a FooID to link them to their category)

+1  A: 

For Foo you will need to override object.GetHashCode() and object.Equals()

The dictionary will call GetHashCode() to calculate a hash bucket for each value and Equals to compare whether two Foo's are identical.

Make sure to calculate good hash codes (avoid many equal Foo objects having the same hashcode), but make sure two equals Foos have the same hash code. You might want to start with the Equals-Method and then (in GetHashCode()) xor the hash code of every member you compare in Equals.

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashColde() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}
froh42
Aside - but xor (^) makes a poor combinator for hash-codes, as it often leads to a lot of diagonal collisions (i.e. {"foo","bar"} vs {"bar","foo"}. A better choice is to multiply and add each term - i.e. 17 * a.GetHashCode() + B.GetHashCode();
Marc Gravell
Marc, I see what you mean. But how do you get at the magic number 17? Is it advantageous to use a prime number as a multiplicator for combining hashes? If so, why?
froh42
+12  A: 

By default, the two important methods are GetHashCode() and Equals(). It is important that if two things are equal (Equals() returns true), that they have the same hash-code. For example, you might "return FooID;" as the GetHashCode() if you want that as the match. You can also implement IEquatable<Foo>, but that is optional:

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

Finally, another alternative is to provide an IEqualityComparer<T> to do the same.

Marc Gravell
+1 and I don't mean to hijack this thread but I was under the impression that GetHashCode() should return FooId.GetHashCode(). Is this not the right pattern?
Ken Browning
@Ken - well, it just needs to return an int that provides the necessary features. Which FooID will do just as well as FooID.GetHashCode(). As an implementation detail, Int32.GetHashCode() is "return this;". For other types (string etc), then yes: .GetHashCode() would be very useful.
Marc Gravell
Thanks! I went with the IEqualityComparer<T> as it was only for the Dicarionary that I needed the overriden methods.
Dana
A: 

You could have the Foo class override GetHashCode(), and have the method return FooID. This will force the Dictionary to use the FooID as the hash key.

Paul Williams
GetHashCode() is always used in combination with Equals(), to handle conflicts. You need to override both.
Marc Gravell
+1  A: 

As you want the FooID to be the identifier for the group, you should use that as key in the dictionary instead of the Foo object:

Dictionary<int, List<Stuff>>

If you would use the Foo object as key, you would just implement the GetHashCode and Equals method to only consider the FooID property. The Name property would just be dead weight as far as the Dictionary was concerned, so you would just use Foo as a wrapper for an int.

Therefore it's better to use the FooID value directly, and then you don't have to implement anything as the Dictionary already supports using an int as a key.

Edit:
If you want to use the Foo class as key anyway, the IEqualityComparer<Foo> is easy to implement:

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

Usage:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());
Guffa
More correctly, int already supports the methods/interfaces required for it to be used as a key. Dictionary has no direct knowledge of int or any other type.
Jim Mischel
I thought about that, but for a variety of reasons it was cleaner and more convenient to use the objects as the dictionary keys.
Dana
Well, it only looks like you are using the object as key, as you are really only using the id as key.
Guffa