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.
Previous Posts in the Series
You might want to read the previous posts in this series, if yes, following are the links to the previous content related to it:
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.