views:

114

answers:

1

I have a problem with hashsets at the moment. I have classes which are immutable and contain just one item, when I add two different classes with the same data to a hashset, I get them both in the set. This is weird, because I've overloaded Equals and GetHashCode on both the base class and the superclass.

public abstract class Contact :IEquatable<Contact>
{
    public readonly BigInteger Id;

    public Contact(BigInteger id) { this.Id = id; }

    public abstract bool Equals(Contact other);

    public abstract int GetHashCode();

    public abstract bool Equals(object obj);
}

And the inheriting class:

public class KeyOnlyContact :Contact, IEquatable<KeyOnlyContact>
{
    public KeyOnlyContact(BigInteger id) :base(id) { }

    public override bool Equals(object obj)
    {
        if (obj is KeyOnlyContact)
            return Equals(obj as KeyOnlyContact);
        else if (obj is Contact)
            return Equals(obj as Contact);
        else
            return (this as object).Equals(obj);
    }

    public override bool Equals(Contact other)
    {
        if (other is KeyOnlyContact)
            return Equals(other as KeyOnlyContact);
        else
            return (this as object).Equals(other as object);
    }

    public bool Equals(KeyOnlyContact other)
    {
        return other.Id.Equals(Id);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

As you can see, all the real work is deferred to the BigInteger which is the id. This is a .net class and I have confirmed I don't get duplicate if I just add BigInteger to a hashset.

To clarify:

BigInteger a;
HashSet<Contact> set;

set.add(new KeyOnlyContact(a));
set.add(new KeyOnlyContact(a));

set.Count == 2
+7  A: 
public abstract int GetHashCode();

You have accidentally re-declared GetHashCode (method-hiding). Remove this declaration and it might start working. When your derived classed override GetHashCode, they are providing this version - they aren't overriding object.GetHashCode, which is what is required.

If you want an abstract GetHashCode, perhaps:

public sealed override int GetHashCode() { return GetHashCodeImpl(); }
protected abstract int GetHashCodeImpl();

Now the derived types must provide GetHashCodeImpl, and they are all mapped to object.GetHashCode.

Marc Gravell
bah, that hides the method rather than requiring that inheriting classes implement it themselves. I'll go and see if that fixes it
Martin
Yes it does. And this is what I get for coding at silly times in the morning on the train :( Thanks very much Marc
Martin
@Martin - you're welcome.
Marc Gravell
Can I not say public override abstract int GetHashCode(); to fix it and require that overriding classes provide their own hashcode?
Martin
@Martin - do you know what - I've never thought to try that, and it seems to work fine. Good show!
Marc Gravell
Woah :O I was expecting you to come back with a reason that looked good but was in fact a terrible idea ;)
Martin