Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEquatables implementation only called if the base Equals is overridden

Tags:

c#

.net

generics

I have the following class

    class Product : IEquatable<Product>
{
    public Guid Id { get; set; }
    public bool Equals(Product other)
    {
        return Id.Equals(other.Id);
    }
}

If i try and create a unique list of the items of a list as follows

            Guid a = Guid.NewGuid();
        List<Product> listA = new List<Product>();
        listA.Add(new Product(){Id = a});

        List<Product> listB = new List<Product>();
        listB.Add(new Product()
        {
            Id = a
        });
        Debug.Assert(listA.Union(listB).Count()==1);

two items are returned, this occurs until I override the object.Equals method, once i do this and my code is as follows

class Product : IEquatable<Product>
{
    public Guid Id { get; set; }
    public bool Equals(Product other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return other.Id.Equals(Id);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Product)) return false;
        return Equals((Product) obj);
    }

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

my IEquatable Equals method is now called, but only if i override the base method, furthermore if i put a breakpoint on the object equals method it is never called.

Why is this?

----UPDATE

So with the product class

    class Product : IEquatable<Product>
{
    public Guid Id
    {
        get;
        set;
    }
    public bool Equals(Product other)
    {
        return Id.Equals(other.Id);
    }

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

}

If GetHashCode is removed, The IEquatable implementation of Equals is never hit I understand you should generally implement Equals and GetHashCode together, is this why?

like image 296
Kev Hunter Avatar asked Sep 30 '11 15:09

Kev Hunter


1 Answers

The problem is in the original implementation you're not overriding the GetHashcode method. Under the hood Union uses a Set<T> style structure to remove duplicates. This structure puts objects into buckets based on the value returned from GetHashCode. If the hash code doesnt't match up between equal objects (which it must do) then they can potentially be put in different buckets and never compared with Equals

In general if you implement IEquatable<T> you should always

  • Override Object.Equals
  • Override Object.GetHashCode

Not doing both will land you in situations like this.

Note, your implementation could be simplified a bit

class Product : IEquatable<Product>
{
  public Guid Id { get; set; }
  public bool Equals(Product other) 
  {
    if (ReferenceEquals(null, other)) {
      return false;
    }
    return other.Id == this.Id;
  }

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

  public override int GetHashCode()
  {
    return Id.GetHashCode();
  }
}
like image 73
JaredPar Avatar answered Oct 11 '22 05:10

JaredPar