Introduction to IEquatable<T> interface in C#

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 solutions to these problems.
 

IEquatable<T> Interface

 
The generic IEquatable<T> exists to solve a slightly different problem with the Equals method. The Equals method on the Object type takes the parameter of the type Object. We know that this is the only type of parameter, which is possible if we want Object.Equals to work for all the types.
 
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 the value type instead of the reference type it is because we are concerned with the performance. Thus, we always want to avoid this performance overhead of boxing and unboxing.
 
There is another problem, having an object as the parameter means that there is no type safety. For example, the following code will compile without any problem:
  1. class Program {  
  2.     static void Main(String[] args) {  
  3.         Person p1 = new Person("Ehsan Sajjad");  
  4.         Program p = new Program();  
  5.         Console.WriteLine(p1.Equals(p));  
  6.         Console.ReadKey();  
  7.     }  
There is nothing to stop me from calling the Equals method on two difference types of instances. We are comparing the instance of the Person class with the instance of the Program, and the compiler didn’t stop me doing this, 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 this kind of comparison in your code and obviously, it would be nice, if the compiler could pick up this kind of situation. Right now, it cannot, because of Object.Equals method does not have strong type safety.
 
We can solve this boxing and type safety issue by having an Equals method, which takes the type being compared as the parameter. For example, we can have an Equals method on the string, which takes a string as the parameter and we can have an Equals method on the Person class, which takes a Person variable as the parameter. This will solve both the boxing and type safety problem nicely.
 
As we talked in the previous post about the problem of inheritance with the above approach, 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.
 
Hence, 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 the 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.
 
returns
 
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 the value types, there will be no boxing.
 
IEquatable<T> and Value Types
 
We can illustrate the IEquatable<T> interface with one of the simplest type of integers:
  1. static void Main(String[] args) {  
  2.     int num1 = 5;  
  3.     int num2 = 6;  
  4.     int num3 = 5;  
  5.     Console.WriteLine(num1.Equals(num2));  
  6.     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 methods for int, one of them takes an object as a parameter and that’s the overridden Object.Equals method, while the other one takes an integer as the parameter. This Equals method is an implementation of IEquatable<int> by integer type and this is the overload, which will be used for the comparison of the above example code, because, in both Equals calls, we are passing an integer as a parameter and not the object, so the compiler will pick the overload, defined for IEquatable<int>, as it is the best signature match.
 
match
 
This is obviously a very unnatural way to compare the integers. Normally, we just write, as shown below:
  1. Console.WriteLine(num1 == num2); 
We have written the code via Equals method so that you can see that there are two Equals methods. All the primitive supports provide the implementation for IEquatable<T> interface. Just take the above example, int implements the IEquatable<int>.
 
example
 
Likewise, the other primitive types also implement IEquatable<T>. Generally, IEquatable<T> is very useful for the value types. Unfortunately, Microsoft had not been very consistent about implementing it for the non-primitive value types in the Framework Class Library. You can’t always rely on this interface to be available.
 
IEquatable<T> and Reference Types
 
.IEquatable<T>
is not that useful for the reference types, as it is for the value types. For the reference types, there is not really any performance issue as we had for the value types (boxing), which needs fixing and also for the reason that IEquatable<T> does not play nicely with the inheritance.
 
It is worth noting here that the string is a reference type implements IEquatable<T>. If you recall from Part 2, when we were demonstrating the Equals method for the string, we were explicitly casting the string variable to the object.
  1. static void Main(String[] args) {  
  2.     string s1 = "Ehsan Sajjad";  
  3.     string s2 = string.Copy(s1);  
  4.     Console.WriteLine(s1.Equals((object) s2));  
Make sure that it calls the Object.Equals override, which takes the object as a parameter. If we don’t do that, the compiler will pick the strongly typed Equals method and that method is actually the implementation of IEquatable<string> implemented by the string. The string is a sealed class so you cannot inherit from it. Thus, the issue of conflict between Equality and Inheritance does not arise.
 
Obviously you would expect that when both Equals methods are available on a type, the virtual Object.Equals method and the IEquatable<T> Equals method 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, 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 the two versions of Equals that behave differently, then developers who will consume your type will get very confused.
 

Summary

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


Similar Articles