Story of Equality in .NET – Part 3

0
59

Introduction

After reading:

you can see that Object.Equals method has two problems. One thing is that it lacks strong typing and for value types, boxing needs to be done. In this post, we will look at the IEquatable<T> interface which provides a solution to these problems.

IEquatable<T> Interface

The generic IEquatable <T> exists for solving a slightly different problem with Equals method. The Equals method on Object type takes parameter of type Object. We know that this is the only type of parameter which is possible if we want Object.Equals to work for all types.

But Object is a reference type which means that if you want to place a value type as an argument, the value type would be boxed which will be a performance hit which is bad. Typically, when we define value type instead of reference type, it is because we are concerned about performance, so we always want to avoid this performance overhead of boxing and unboxing.

There is also another problem, having an object as parameter means that there is no type safety. For example, the following code will compile without any problem:

class Program
{ static void Main(String[] args) { Person p1 = new Person("Ehsan Sajjad"); Program p = new Program(); Console.WriteLine(p1.Equals(p)); Console.ReadKey(); }
}

There is nothing to stop me for calling Equals method on two different types of instances. We are comparing instance of Person class with instance of Program and compiler does not stop me from doing that, which is clearly an issue as both are totally different types and there is no way they can meaningfully equal each other.

This was just an example, you should not be doing these kind of comparisons in your code, and obviously it would be nice if compiler could pick up this kind of situation, right now it cannot because Object.Equals method does not have strong type safety.

We can solve this boxing and type safety issue by having an Equals method that takes the type being compared as parameter, so for example we can have an Equals method on String which takes a string as parameter and we can have an Equals method on Person class which takes a Person variable as parameter. This will solve both boxing and type safety problem nicely.

We talked in the previous post about the problem of inheritance with the above approach. But there is no way to usefully define these strongly typed methods on System.Object, because System.Object does not know what types will be deriving from it.

So how can we make a strongly typed Equals method generally available to consume. Microsoft solved this problem by providing the interface IEquatable<T> which can be exposed by any type that wants to provide strongly typed Equals method. If we look at the documentation, we can see that IEquatable<T> exposes just one method called Equals which returns a bool.  

This serves exactly the same purpose as Object.Equals, but it takes the generic type T instance as a parameter and therefore it is strongly typed which means for value types, there will be no boxing to be done.

IEquatable<T> and Value Types

We can illustrate the IEquatable<T> interface with one of the simplest type integer.

 

static void Main(String[] args)
{ int num1 = 5; int num2 = 6; int num3 = 5; Console.WriteLine(num1.Equals(num2)); Console.WriteLine(num1.Equals(num3)); }

We have three integer variables which we are comparing using Equals and printing the result on the console. If we look at the intellisense, we can see that there are two Equals method for int, one of them takes object as parameter and that’s the overridden Object.Equals method, other one takes an integer as parameter, this Equals method is implementation of IEquatable<int> by integer type, and this is the overload which will be used for comparison of the above example code, because in both Equals call, we are passing integer as parameter not object, so the compiler will pick the overload defined for IEquatable<int> as it is the best signature match.

This is obviously a very unnatural way to compare integers, normally we just write like:

Console.WriteLine(num1 == num2);

We have written the code via Equals method so that you can see that there are two Equals method out there. All primitive supports provide the implementation for IEquatable<T> interface. Just take the above example, int implements the IEquatable<int>.

 

Likewise, other primitive types also implement IEquatable<T>. Generally IEquatable<T> is very useful for value types. Unfortunately, Microsoft had not been very consistent about implementing it for non-primitive value types in the Framework Class Library, so you can’t always rely on this interface to be available.

IEquatable<T> and Reference Types

IEquatable<T> is not that useful for reference types as it is for value types. Because for reference types, there are not really any performance issues like we had for value types (boxing) which needs fixing and also for the reason that IEquatable<T> does not play nicely with inheritance.

But it is worth noting here that String which is a reference type does implement IEquatable<T>. If you recall from Part – 2, when we were demonstrating the Equals method for string, we were explicitly casting the string variable to object.

 

static void Main(String[] args)
{ string s1 = "Ehsan Sajjad"; string s2 = string.Copy(s1); Console.WriteLine(s1.Equals((object)s2)); }

That was to make sure that it calls the Object.Equals override which takes object as parameter, if we don’t do that, then the compiler will pick the strongly typed Equals method and that method is actually the implementation of IEquatable<string> implemented by String. String is a sealed class so you cannot inherit from it, so the issue of conflict between Equality and Inheritance does not arise.

Obviously, you would expect that when both Equals method are available on a type, the virtual Object.Equals method and the IEquatable<T> Equals method, they should always give the same result. That’s’ true for all the Microsoft implementations and it’s one of the things that is expected of you when you implement this interface yourself.

If you want to implement IEquatable<T> interface, then you should make sure that you override the Object.Equals method to do exactly the same thing as your interface method does and that makes sense, because it should be clear that if a type implements two versions of Equals that behave differently, then developers who will consume your type will get very confused.

You Might Also Like to Read

Summary

We saw that we can implement IEquatable<T> on our types to provide a strongly typed Equals method which also avoids boxing for value types. IEquatable<T> is implemented for primitive numeric types but unfortunately Microsoft has not been very proactive implementing for other value types in the Framework Class Library.

LEAVE A REPLY