Fredou
Fredou

Reputation: 20120

linq & distinct, implementing the equals & gethashcode

so I'm trying to make this work and I can't seem to know why it doesn't work

demo code;

namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
        var myVar = new List<parent >();
        myVar.Add(new parent() { id = "id1", blah1 = "blah1", c1 = new child() { blah2 = "blah2", blah3 = "blah3" } });
        myVar.Add(new parent() { id = "id1", blah1 = "blah1", c1 = new child() { blah2 = "blah2", blah3 = "blah3" } });

        var test = myVar.Distinct();

        Console.ReadKey();

    }
}


public class parent : IEquatable<parent>
{
    public String id { get;set;}
    public String blah1 { get; set; }
    public child c1 { get; set; }

    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + id.GetHashCode();
            hash = hash * 23 + blah1.GetHashCode();
            hash = hash * 23 + (c1 == null ? 0 : c1.GetHashCode());
            return hash;
        }
    }

    public bool Equals(parent other)
    {
        return object.Equals(id, other.id) &&
            object.Equals(blah1, other.blah1) &&
            object.Equals(c1, other.c1);
    }

}

public class child : IEquatable<child>
{
    public String blah2 { get; set; }
    public String blah3 { get; set; }

    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            // Suitable nullity checks etc, of course :)
            hash = hash * 23 + blah2.GetHashCode();
            hash = hash * 23 + blah3.GetHashCode();
            return hash;
        }
    }

    public bool Equals(child other)
    {
        return object.Equals(blah2, other.blah2) &&
            object.Equals(blah3, other.blah3);
    }

}
}

anyone could spot my error(s) ?

Upvotes: 7

Views: 8293

Answers (4)

Steve Lautenschlager
Steve Lautenschlager

Reputation: 658

There are several things to get right here. If I'm going to implement any aspect of equality in a class such as GetHashCode, overriding == or IEquatable, I always use the following pattern.

  1. Override Equals
  2. Override GetHashCode
  3. Implement IEquatable<T> which means implementing Equals(T)
  4. Implement !=
  5. Implement ==

So, if I had a class named ExpiryMonth with properties Year and Month, this is how that implementation would look. It's a fairly mindless task now to adapt for other types of classes.

I have based this pattern on several other stackoverflow answers which all deserve credit, but which I haven't tracked along the way.

By always implementing all of these elements together, it ensures proper equality operations in a variety of contexts including dictionaries and Linq operations.

    public static bool operator !=(ExpiryMonth em1, ExpiryMonth em2)
    {
        if (((object)em1) == null || ((object)em2) == null)
        {
            return !Object.Equals(em1, em2);
        }
        else
        {
            return !(em1.Equals(em2));
        }
    }
    public static bool operator ==(ExpiryMonth em1, ExpiryMonth em2)
    {
        if (((object)em1) == null || ((object)em2) == null)
        {
            return Object.Equals(em1, em2);
        }
        else
        {
            return em1.Equals(em2);
        }
    }
    public bool Equals(ExpiryMonth other)
    {
        if (other == null) { return false; }
        return Year == other.Year && Month == other.Month;
    }
    public override bool Equals(object obj)
    {
        if (obj == null) { return false; }
        ExpiryMonth em = obj as ExpiryMonth;
        if (em == null) { return false; }
        else { return Equals(em); }
    }
    public override int GetHashCode()
    {
        unchecked // Overflow is not a problem
        {
            var result = 17;
            result = (result * 397) + Year.GetHashCode();
            result = (result * 397) + Month.GetHashCode();
            return result;
        }
    }

Upvotes: 1

SLaks
SLaks

Reputation: 887469

You need to override the Equals(object) method:

public override bool Equals(object obj) {
    return Equals(obj as parent);
}

The object.Equals method (unlike EqualityComparer<T>.Default) does not use the IEquatable interface. Therefore, when you write object.Equals(c1, other.c1), it doesn't call your Child.Equals(Child) method.

You don't absolutely need to do that for parent as well, but you really should.

Upvotes: 5

ulrichb
ulrichb

Reputation: 20054

Either you do what SLaks suggests, or you use EqualityComparer<child>.Default in your parent class to use your IEquatable<child> implementation:

  public bool Equals(parent other)
  {
   return object.Equals(id, other.id) &&
    object.Equals(blah1, other.blah1) &&
    EqualityComparer<child>.Default.Equals(c1, other.c1);
 }

Upvotes: 3

Jason
Jason

Reputation: 220

When adding the calculating the hash you might want to try something like

hash ^= id.GetHashCode();

Not sure if that is what is causing your issue.

Upvotes: 0

Related Questions