Introduction
In this article, I will show you what the IEquatable
interface is and how to implement it properly. I'll also show you why it is important to override the Object.Equals
method when implementing the interface.
The IEquatable Interface
To check the equality between two classes, the System.Object
class contains two Equals
methods with the following definitions:
public virtual bool Equals(object obj)
public static bool Equals(object objA, object objB)
These Equals
methods are used to check the equality between two objects. The static
method exists to be able to work with null
values. If objA
is null
and you would perform objA.Equals(objB)
, you will get a NullReferenceException
. Therefore, the static
method performs additional null
checks. Below are the steps that are performed when using the static
method.
- check if
objA
and objB
are identical: returns true
if both objects refer to the same memory location or both objects are null
- check if
objA
or objB
is null
: returns false
if either one of the objects is null
- compare the values of
objA
and objB
by using the objA.Equals(objB)
method
If you want to have your own implementation, you can override this method. Since the Equals
method has a parameter of type Object
, a cast will be needed in order to be able to access class specific members.
This is where the IEquatable
interface comes in. The IEquatable
is a new generic interface in .NET 2.0 that allows you to do the same as the System.Object.Equals
method but without having to perform casts. So, when implementing this interface, you can reduce the number of casts, which is a good thing for performance. Especially when working a lot with generic collections, since generic collections make use of this equality comparison in some of their methods (List<T>.Equals()
, List<T>.IndexOf()
, List<T>.LastIndexOf()
, ...).
Note: Since value types are not Object
s and the Equals
methods require Object
s as parameters, boxing and unboxing will occur when performing an equality check. See paragraph "Value Types" on how to avoid this.
The Problem
A lot of people don't override the object.Equals
method and only implement the IEquatable.Equals
method. At first, nothing seems wrong with that, but when you look further, you will see that you can have strange results. Depending on the way you check the equality between two objects, the .NET runtime can use different paths to check the equality of both objects.
If you don't implement the interface, the runtime will use the Object.Equals
method. When you implement the IEquatable
interface, the runtime will use the IEquatable.Equals
method only if you pass an object with the same type as defined in the interface implementation; otherwise, it will use the Object.Equals
method.
To make it easier to understand, I've created a sample application that shows you when the runtime uses which path to check the equality between two objects.
First, I create a class named Employee
which implements the IEquatable
interface. This class contains two properties: Name
and Account
. Just as an example, I consider two instances of the Employee
class to be the same from the moment that they have the same value for the Account
property (independent of their Name
property). This is implemented in the Equals
method, which you need to create when implementing the IEquatable
interface.
public class Employee : IEquatable<Employee>
{
public string Account;
public string Name;
public Employee(string account, string name)
{
this.Account = account;
this.Name = name;
}
public bool Equals(Employee other)
{
if (other == null) return false;
return (this.Account.Equals(other.Account));
}
}
In a console application, I then create two instances of the Employee
class and give them the same values for their Account
property and different values for their Name
property. I will then check for equality in different ways:
class Program
{
public static void Main()
{
Employee emp1 = new Employee("GEVE", "Geert Verhoeven");
ArrayList employees = new ArrayList();
employees.Add(emp1);
Employee emp2 = new Employee("GEVE", "Geert");
Console.WriteLine("emp1.Equals(emp2): {0}", emp1.Equals(emp2));
Object obj = emp2;
Console.WriteLine("emp1.Equals(obj): {0}", emp1.Equals(obj));
Console.WriteLine("employees.Contains(emp2): {0}",
employees.Contains(emp2));
Console.ReadLine();
}
}
Output:
Explanation
- Scenario 1: Comparing with an object of the same class
In the first scenario, I simply use the Employee.Equals
method. If you use the debugger, you can see that the interface is used by executing the Equals
method which will return true
.
- Scenario 2: Comparing with an object from a different class
In the second scenario, I first do a cast of the Employee
object and assign it to a new instance of the Object
class. If you then use the debugger, you will see that it doesn't enter the Equals
method.
This is because, at this moment, the runtime is using the Equals
method from the Object
class. Since the Object.Equals
method doesn't contain our custom validation rules, the result will be false
.
- Scenario 3: Using an
ArrayList.Contains
method to look for an object in the list (analog to 2) In the third scenario, I use the ArrayList.Contains
method. If you look at the method definition, you can see that it uses an Object
as parameter.
Because of this, the ArrayList.Contains
method uses the same logic as in the second scenario by using the Object.Equals
, and will return false
.
The Solution
To avoid having different results depending on the way you check the equality of the objects, you should override the Object.Equals
method in your Employee
class. This ensures that whenever a class doesn't use the IEquatable.Equals
method, still the same checks will be performed. When overriding the Object.Equals
, it is a best practice to also override the Object.GetHashCode
method. You can do this by adding the following code to the Employee
class:
public override int GetHashCode()
{
return this.Account.GetHashCode();
}
public override bool Equals(object obj)
{
Employee emp = obj as Employee;
if (emp != null)
{
return Equals(emp);
}
else
{
return false;
}
}
If you now run the application again, you will see that for scenario 2 and 3, the overridden Equals
method will be used and that all scenarios will return true
.
Value Types
When implementing the IEquatable
interface on value types, you also need to overload the equality (==) and inequality (!=) operators. This is important to avoid boxing and unboxing when doing an equality check. You can implement this by adding the following code to the Employee
class:
public static bool operator ==(Employee emp1, Employee emp2)
{
if (object.ReferenceEquals(emp1, emp2)) return true;
if (object.ReferenceEquals(emp1, null)) return false;
if (object.ReferenceEquals(emp2, null)) return false;
return emp1.Equals(emp2);
}
public static bool operator !=(Employee emp1, Employee emp2)
{
if (object.ReferenceEquals(emp1, emp2)) return false;
if (object.ReferenceEquals(emp1, null)) return true;
if (object.ReferenceEquals(emp2, null)) return true;
return !emp1.Equals(emp2);
}
History
- 21 September 2007: Original article.
- 25 September 2007:
- Added the "The IEquatable Interface" paragraph.
- Changed the
IEquatable.Equals
method to work with null
values. - Changed the operator
==
and operator !=
implementation to work with null
values. - Updated the demo code to reflect the changes in the document.