What is the difference between using IEqualityComparer and Equals/GethashCode Override?
Asked Answered
D

4

26

When i am using dictionaries sometimes I have to change the default Equals meaning in order to compare Keys. I see that if I override the Equals and GetHashCode on the key's class or i create a new class which implements IEqualityComparer I have the same result. So what's the difference between using IEqualityComparer and Equals/GethashCode Override? Two Examples:

class Customer
{
    public string name;
    public int age;
    public Customer(string n, int a)
    {
        this.age = a;
        this.name = n;
    }
    public override bool Equals(object obj)
    {
        Customer c = (Customer)obj;
        return this.name == c.name && this.age == c.age;
    }
    public override int GetHashCode()
    {
        return (this.name + ";" + this.age).GetHashCode();
    }
}
  class Program
{
    static void Main(string[] args)
    {
        Customer c1 = new Customer("MArk", 21);
        Customer c2 = new Customer("MArk", 21);
        Dictionary<Customer, string> d = new Dictionary<Customer, string>();
        Console.WriteLine(c1.Equals(c2));
        try
        {
            d.Add(c1, "Joe");
            d.Add(c2, "hil");
            foreach (KeyValuePair<Customer, string> k in d)
            {
                Console.WriteLine(k.Key.name + " ; " + k.Value);
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("Chiave già inserita in precedenza");
        }
        finally
        {
            Console.ReadLine();
        }
    }
}

}

Second one :

class Customer
{
    public string name;
    public int age;
    public Customer(string n, int a)
    {
        this.age = a;
        this.name = n;
    }
}
class DicEqualityComparer : EqualityComparer<Customer>
{
    public override bool Equals(Customer x, Customer y) // equals dell'equalitycomparer
    {
        return x.name == y.name && x.age == y.age;
    }
    public override int GetHashCode(Customer obj)
    {
        return (obj.name + ";" + obj.age).GetHashCode();
    }
}
class Program
{
    static void Main(string[] args)
    {
        Customer c1 = new Customer("MArk", 21);
        Customer c2 = new Customer("MArk", 21);
        DicEqualityComparer dic = new DicEqualityComparer();
        Dictionary<Customer, string> d = new Dictionary<Customer, string>(dic);
        Console.WriteLine(c1.Equals(c2));
        try
        {
            d.Add(c1, "Joe");
            d.Add(c2, "hil");
            foreach (KeyValuePair<Customer, string> k in d)
            {
                Console.WriteLine(k.Key.name + " ; " + k.Value);
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("Chiave già inserita in precedenza");
        }
        finally
        {
            Console.ReadLine();
        }
    }
}

}

Both examples have the same result.

Thanks in advance.

Dolmen answered 16/6, 2013 at 20:7 Comment(2)
Possible dupe / similar: #7751670Discussant
Because there is more than one way to compare some objects.Steeve
F
22

When you override Equals and GetHashCode you are changing the way the object will determine if it is equals to another. And a note, if you compare objects using == operator it will not have the same behavior as Equals unless you override the operator as well.

Doing that you changed the behavior for a single class, what if you need the same logic for other classes? If you need a "generic comparison". That is why you have IEqualityComparer.

Look at this example:

interface ICustom
{
    int Key { get; set; }
}
class Custom : ICustom
{
    public int Key { get; set; }
    public int Value { get; set; }
}
class Another : ICustom
{
    public int Key { get; set; }
}

class DicEqualityComparer : IEqualityComparer<ICustom>
{
    public bool Equals(ICustom x, ICustom y)
    {
        return x.Key == y.Key;
    }

    public int GetHashCode(ICustom obj)
    {
        return obj.Key;
    }
}

I have two different classes, both can use the same comparer.

var a = new Custom { Key = 1, Value = 2 };
var b = new Custom { Key = 1, Value = 2 };
var c = new Custom { Key = 2, Value = 2 };
var another = new Another { Key = 2 };

var d = new Dictionary<ICustom, string>(new DicEqualityComparer());

d.Add(a, "X");
// d.Add(b, "X"); // same key exception
d.Add(c, "X");
// d.Add(another, "X"); // same key exception

Notice that I didn't have to override Equals, GetHashCode in neither of the classes. I can use this comparer in any object that implements ICustom without having to rewrite the comparison logic. I can also make an IEqualityComparer for a "parent class" and use on classes that inherit. I can have comparer that will behave in a different way, I can make one to compare Value instead of Key.

So IEqualityComparer allows more flexibility and you can implement generic solutions.

Fourthly answered 29/6, 2013 at 13:11 Comment(10)
Simply put, IEqualityComparer externalizes the comparison logics while overriding Equals/GetHashCode internalizes them - The same principle/difference for IComparable(internalize) and IComparer(Externalize).Wheal
@h9uest: well said. I wonder if IEqualityComparer can be internalized as well. Equals/GetHashCode not only internalizes comparison logic, but also globalize them. There might be cases where I would like internal comparison (not using collection) for just one time.Begorra
@Begorra I'm afraid IEqualityComparer was designed to externalize comparison. Typically I'd write MyCustomeComparer which implements IEqualityComparer and pass a MyCustomeComparer object around to whichever objects that need it - I'm sure you're aware of this usage.Wheal
@Begorra In addition, I'm not sure why you want to internalize the comparison logics for just one time. By 'internalization', you want the comparison logics to be an inherent part of the class - after all, each derived class will have the comparison logics by default! So chances are that you want to tweak your model a bit? yes? no?Wheal
@Wheal to which objects do you pass MyCustomeComparer to? Collections like the above answer?Begorra
@Wheal The reason to need object comparison just one time is similar to collections, maybe different objects of the same type want to utilize different comparison logic. As an contrived example, person objects, for one object, I may want to compare based on full name, another may want to compare by its last name.Begorra
Thats 50% of the correct answer, the other 50% is that in different situations an object might be equal to another but not in others, eg you have a person class , if person A and Person B are both Male then they many be classed as Equal in a gender search in this cast overriding the object.Equals would be a very bad ideaStowage
can you please update the link to github? seeems broken !Longsufferance
@k2ibegin I no longer have that file, but the most relevant code is on the post.Fourthly
So, to make any operation use IEqualityComparer I need to provide it to it as parameter that means the methods that do not accept IEqualityComparer need Equals/GetHashCodeArithmetic
C
4

The object's Equals() anf GetHashCode() implement the concept of equality intrinsic to the object. However, you might want to use alternative concepts of equality - for example, an equality comparer for address objects that only uses the ZIP code rather than the full address.

Conation answered 16/6, 2013 at 20:19 Comment(0)
D
1

It is essentially the same for this purpose with one subtle difference. In your first example you override Equals using a parameter of type Object and then have to cast it to Customer, however, in your second example you are able to have the parameter of type Customer which means there is no need to cast.

This means that overriding Equals allows comparison between two objects of different types (which may be needed in certain circumstances), however, implementing IEqualityComparer does not give this freedom (which may also be needed in certain circumstances).

Declamation answered 16/6, 2013 at 20:17 Comment(0)
D
1

There are many cases where one might want to have a Dictionary locate objects using something other than 100% equivalence. As a simple example, one may wish to have a dictionary which matches in case-insensitive fashion. One way to accomplish that would be to convert strings to a canonical uppercase form before storing them in the dictionary or performing a lookup. An alternative approach is to supply the dictionary with an IEqualityComparer<string> which will compute hash-codes and check for equality in some sort of case-independent function. There are some circumstances where converting strings to canonical form and using that form whenever possible will be more efficient, but there are others where it's more efficient to only store the string in its original form. One feature I wish .NET had which would improve the usefulness of such dictionaries would be a means of requesting the actual key object associated with a given key (so if the dictionary contained the string "WowZo" as a key, one could look up "wowzo" and get "WowZo"; unfortunately, the only way to retrieve an actual key object, if TValue doesn't contain a redundant reference to it, is to enumerate the entire collection).

Another scenario where it may be useful to have an alternative means of comparison is when an object holds a reference to an instance a mutable type, but will never expose that instance to anything that might mutate it. In general, two instances of int[] that hold the same sequence of values will not be interchangeable, since it would be possible that in future one or both of them might be changed to hold different values. On the other hand, if a dictionary will be used to hold and look up int[] values, each of which will be the only reference anywhere in the universe to an instance of int[], and if none of the instances will be modified nor exposed to outside code, it may be useful to regard as equal array instances which hold identical sequences of values. Since Array.Equals tests for strict equivalence (reference equality), it would be necessary to use some other means of testing the arrays for equivalence.

Desireah answered 30/6, 2013 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.