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.
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.
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.
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).
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.