Question

I've got a class which consists of two strings and an enum. I'm trying to use instances of this class as keys in a dictionary. Unfortunately I don't seem to be implementing IEquatable properly. Here's how I've done it:

public enum CoinSide
{
    Heads,
    Tails
}

public class CoinDetails : IComparable, IEquatable<CoinDetails>
{
    private string denomination;
    private string design;
    private CoinSide side;

//...

    public int GetHashCode(CoinDetails obj)
    {
        return string.Concat(obj.Denomination, obj.Design, obj.Side.ToString()).GetHashCode();
    }

    public bool Equals(CoinDetails other)
    {
        return (this.Denomination == other.Denomination && this.Design == other.Design && this.Side == other.Side);
    }
}

However, I still can't seem to look up items in my dictionary. Additionally, the following tests fail:

    [TestMethod]
    public void CoinDetailsHashCode()
    {
        CoinDetails a = new CoinDetails("1POUND", "1997", CoinSide.Heads);
        CoinDetails b = new CoinDetails("1POUND", "1997", CoinSide.Heads);
        Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
    }

    [TestMethod]
    public void CoinDetailsCompareForEquality()
    {
        CoinDetails a = new CoinDetails("1POUND", "1997", CoinSide.Heads);
        CoinDetails b = new CoinDetails("1POUND", "1997", CoinSide.Heads);
        Assert.AreEqual<CoinDetails>(a, b);
    }

Would someone be able to point out where I'm going wrong? I'm sure I'm missing something rather simple, but I'm not sure what.

Was it helpful?

Solution

You class has to override Equals and GetHashCode:

public class CoinDetails 
{
    private string Denomination;
    private string Design;
    private CoinSide Side;

    public override bool Equals(object obj)
    {
        CoinDetails c2 = obj as CoinDetails;
        if (c2 == null)
            return false;
        return Denomination == c2.Denomination && Design == c2.Design;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + (Denomination ?? "").GetHashCode();
            hash = hash * 23 + (Design ?? "").GetHashCode();
            return hash;
        }
    }
}

Note that i've also improved your GetHashCode algorithm according to: What is the best algorithm for an overridden System.Object.GetHashCode?

You could also pass a custom IEqualityComparer<CoinDetail> to the dictionary:

public class CoinComparer : IEqualityComparer<CoinDetails>
{
    public bool Equals(CoinDetails x, CoinDetails y)
    {
        if (x == null || y == null) return false;
        if(object.ReferenceEquals(x, y)) return true;
        return x.Denomination == y.Denomination && x.Design == y.Design;
    }

    public int GetHashCode(CoinDetails obj)
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + (obj.Denomination ?? "").GetHashCode();
            hash = hash * 23 + (obj.Design ?? "").GetHashCode();
            return hash;
        }
    }                      
}

Now this works and does not require CoinDetails to override Equals+GetHashCode:

var dict = new Dictionary<CoinDetails, string>(new CoinComparer());
dict.Add(new CoinDetails("1POUND", "1997"), "");
dict.Add(new CoinDetails("1POUND", "1997"), ""); // FAIL!!!!
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top