C# equality in depth. Part 3: IEquatable

With .NET 2.0, Microsoft introduced the IEquatable interface. It introduced a typesafe Equals() method that could be used to speed up comparisons in the new generic collections. It speeded up those comparisons by avoiding the need for casting and boxing, which the Object.Equals() suffered from. The IEquatable interface is a dangerous beast though as it can lure the unwary into thinking it rationalised equality in C#. This set of tests sets out to demonstrate how quite the opposite occurred: it simply made things worse.

This is part two of a three part series on C# equality. See part one (basic == equality) and part two (.Equals() and == are not equal) for the rest of the series. The series is generated from code, which can be obtained from https://github.com/DavidArno/CSharpEqualityInDepth.

To start, we define a simple immutable value class that implements IEquatable: XY. The class implements .Equals(), which is true if the X and Y values are equal.

Two separate instances of XY are equal if their X’s and Y’s are equal.

If the X values are different, then they are not equal.

If the Y values are different, again they are not equal.

However, there is a problem lurking below the surface. The IEquatable.Equals() method is an overload, not an override of Object.Equals(). The compiler must therefore choose – at compile-time – the appropriate version of Equals() to use. If we define xy1 as an object at compile time, then Object.Equals() is used for the comparison. This causes the comparison to be a by-reference comparison.

To overcome this problem, it is necessary to override Object.Equals in our class. The new version of the XY class (XY2) does just that.

Now when we compare two XY2 as objects, they are correctly tested for equality by value.

Of course, just because xy1.Equals(xy2) doesn’t mean that xy1 == xy2. The == operator still compares by reference.

So let’s define a final version of XY, XY3, which handles equality as near as possible to perfectly as C# will allow. We therefore add == and != operators.

Now xy1 == xy2 is true.

Why the comment “as near as possible to [perfect] as C# will allow”? Well if we define xy1 as object, the wrong version of == gets used and a compare-by-reference sneaks back in unwanted, but unavoidable.

Posted in C#