Search code examples
c#iequalitycomparer

Why can I not use customer.Name.contains("smith") in IEqualityComparer<Customer> Equals method


I want to use the HashSet.Contains method because its super fast.

var hashset = new HashSet<Customer>(customers, new CustomerComparer());
var found = hashset.Contains(new Customer{ Id = "1234", Name = "mit" }); // "mit" instead of an equals "smith" in the comparer.

I am searching for multiple properties on the customer object.

I have to implement the IEqualityComparer interface like:

public class CustomerComparer : IEqualityComparer<Customer>
{
    public bool Equals(Customer x, Customer y)
    {
        return x.Id == y.Id && x.Name.Contains(y.Name);
    }      

    public int GetHashCode(Customer obj)
    {
        return obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
    }
}

Why is the Equals method never hit when I do NOT use an Equals method inside the CustomerComparer Equals method like the .Contains?


Solution

  • The way you have implemented the equality comparer can not work properly. The reason is how the hash set and the equality comparer work internally. When a Dictionary or a HashSet does a comparison of items, it will first call GetHashCode on both items. Only if these hash codes match, it will confirm the exact match with a subsequent call to Equals to avoid false matches in case of a hash code collision. If you use your example, (x.Name = "smith" and y.Name = "mit"), the GetHashCode method will return different hash codes for each item and Equals is never called.

    The solution in this case is to only use the Id for hash code creation. That would degrade performance a bit because you would have to call Equals more often to resolve collision, but that's the price you have to pay:

    public int GetHashCode(Customer obj)
    {
        return obj.Id.GetHashCode() ;
    }
    

    What you should also consider that you have no guarantee whether your existing item will be x or y. So you have to use Contains in both directions:

    public bool Equals(Customer x, Customer y)
    {
        return x.Id == y.Id && (x.Name.Contains(y.Name) || y.Name.Contains(x.Name));
    }