IEquatable<T> seems to be a simple enough interface to implement. It just has one method
- public bool Equals(MyClass other)
That seems easy enough!! But once you get into all the rules around this, it is a minefield and needs to be implemented with a lot of care. Also information on how to implement this is spread all over the web and it is quite difficult to get all the data in one place.
You get an instance of the class as a parameter and you just compare other with the data you have in your current instance and return true or false.
Well, ok you have that code in place, how do you use it?
MyClass a, b;
// Code that setup data for MyClass instances
//
MyClass.Equals(a, b);
You think this should work? Well it will not give you any errors, but your Equals function is not called? Why, because this calls the Object.Equals method, rather than your equals (so you are actually doing a Reference equality rather than your custom logic). The way to call the IEquality.Equals (as defined above) is
Well, that’s why the MSDN topic on IEquatable clearly states:
If you implement IEquatable<T>, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable<T>.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. In addition, you should overload the op_Equality and op_Inequality operators. This ensures that all tests for equality return consistent results.
This is to ensure that Object.Equals, Instance.Equals, == and != all work in the same way and the you don’t have unintentional side effects.
I prefer to write a static check method that can be called from all the overrides. Again writing a equality function is more complicated than it sounds. A rough template of my equality check is as below
public static bool EqualsCheck(MyClass a, MyClass b)
{
//Check 1
//If both are the same instance of an object then return true
//Object reference also returns true if both objects are null
if (object.ReferenceEquals(a, b))
return true;
//Check 2
//IF one of the object is null and
//the other not null, then equality fails
if (a == null ^ b == null)
return false;
//Check 3
// Actual logic to check equality of the two objects
}
Once all the overrides are wired up for Equality, you still have one override still left. That of GetHashCode. This is a tricky item as it has some strict rules that have a direct impact on usage in any Hashed collection.
If you have an property/field that will not change during the lifetime of this class, then you can just use the GetHashCode of that property and return that. Also you need to ensure that this property is not changed via maybe a private set/readonly setting.
If you don’t want to get into implement your own Hash Algorithm, then return 1. This follows all the rules, but has a performance impact when this class is part of hashed collection (e.g. Dictionary).
Also there are some gotchas to be aware of using this in a base class. So the recommendation is to use this only on sealed classes. This post has the details on the problems using this in base classes.
So think twice and see if your class design really needs IEquatable and then ensure you implement it fully or you may end up with unforeseen and hard to debug bugs.