Introduction
This post is to focus on implementing Equality in Value Types, i.e., overriding Equality behavior for our own implemented Value Types which is basically defining how to decide two objects of a type are equal or not.
Background
As we are now aware of the fact that the technique for checking equality for Value Types and Reference Types is different, it would be better if we discuss both of them separately. That’s why we will be focusing on each of them in separate posts to have a clear understanding on how we can override the Equality for both of these.
This post will particularly focus on the Value Types and for Reference Types we will see in some future post.
Why We Need It?
The first question that might come to mind is “Why we should override the Equality for Value Type”. We will see with a simple example and it will help you understand the fact that overriding the Equality behavior for Value Types is always a good idea indeed. We will define a struct
later down the road and will implement the Equality of it.
Previous Posts on the Topic
You might want to read the previous posts in this series, if yes, following are the links to the previous content related to it:
Steps to Implement Equality for Value Type
For overriding the Equality for a Value Type, there are some necessary steps that should be performed which are mentioned below.
We will need to:
override
the virtual Equals
method of Object
- implement
IEquatable<T> interface
and provide the implementation of Equals()
method of it - provide the overloaded implementation for
<span style="font-size: small">==</span>
and !=
operator method - override the
GetHashCode
method of Object
class
Possible Reasons for implementing Equality
So before we start discussing how we can implement Equality for a Value Type, let’s think a little on what are the possible reasons that would make us think that we should define our own Equality behavior for a Value Type instead of using the default one that framework already provides.
So why would you want to override it? Following are the main reasons for that:
- We want to be able use == operator for comparing the objects of our Value Type. If you remember, we discussed previously that == operator does not work for value types and to make it work, we need to do some implementation (i.e., overload == operator) in our particular Value Type.
- We have also seen previously that the framework provided implementation uses Reflection for checking the values of each field of that type which is obviously affecting the performance as Reflection is slow which results in poor performance of code.
- We also sometimes need different behavior for comparison of a particular type, though this is not usually required but can be needed in some cases, as the default behavior for Value Types considers two objects to be equal if all of their fields also have the same content which is absolutely fine most of the time.
It is a recommended approach to implement Equality for your own defined Value Types that would be used further in your code base. Other developers using your defined Value Type would not get surprised or frustrated when trying to compare two objects of it by using == operator and came to know that they can’t do it.
We can skip overriding the equality for those types that we know we will be using internally in the code base and won’t be exposed to complete code base for usage and we know what we wouldn’t be needing to compare the objects frequently, so implementing equality for those types is not that much needed and would be a waste of time as a result.
Example
We will create an example struct
named Employee
to illustrate about implementing Equality for a Value Type. This is how our Employee struct
looks like now:
public struct Employee
{
public string Name { get; }
public Gender Gender { get; set; }
public Department Department { get; set; }
public Employee(string name, Gender gender, Department department)
{
Name = name;
Gender = gender;
Department = department;
}
public override string ToString()
{
return Name;
}
}
public enum Department
{
HumanRecource,
QualityAssurance,
SoftwareDevelopment,
ProjectManagement,
ITOperations
}
public enum Gender
{
Male,
Female
We have a very simple Employee
type as example which has a String
property which will contain the name of employee and one enumeration property to hold the gender of employee and another enumeration for Department which will be used to track the Department of the employee. We will define our own implementation later to dictate how to decide if two employee instances are equal or not.
There are multiple things that we need to take care and implement in our Employee
type for implementing Equality. The first thing that we need to do is override the Object.Equals()
method so that instead of default implementation getting invoked which is obviously slow because of Reflection involved, by providing implementation for this method it will enhance the performance of the Type, so overriding Equals
method will eliminate the equality check using Reflection and will make it efficient and we will be able to see significant performance improvement.
But wait, Object.Equals()
method takes parameter of type Object
which means that boxing will happen and we know it also hurts the performance of our code, as boxing and unboxing has its own cost involved, so here we would want to avoid that one as well some way.
For avoiding both Reflection and boxing, we will need to implement the IEquatable<Employee> interface
for our type Employee
. Now we will have efficient implementation of Equals()
method in comparison with the framework provided one and another plus point of doing this is that now we have a much better implementation which is also type safe.
We will need to provide the overloaded implementation for ==
and !=
operators as it is normally considered a good practice to do all of the following things when overriding Equality:
Object.Equals
method overriding - Implementing
IEquatable<T> interface
for that type - Implementing
==
and !=
overloaded methods Object.GetHashCode
method overriding
This is important to do because it will make sure that checking for equality for two objects of that type will give the same result, otherwise it can be confusing for the calling code who will be utilizing our type and can cause problems in future.
For example, if we just implement the overloads for ==
and !=
operator and we don’t provide the override
for Object.Equals
method, then what will happen is that the result of Equals
method can be different from the result of ==
operator which will be troublesome for the code that will be utilizing our types.
There is also another method in Object
class called GetHashCode
, whenever we override the Object.Equals
method for a type, then another thing that must be done is overriding the GetHashCode
method as well.
Step 1 - Implementing IEquatable<T> Interface
Now let’s jump in to the Visual Studio and let’s start implementing the IEquatable<Employee> interface
for our Person
type. We will need to inherit our struct
from IEquatable<Employee>
and using the code refactoring feature of Visual Studio:
It will add the Equals
method without any implementation for the IEquatable<Employee>
which would look like:
public bool Equals(Employee other)
{
throw new NotImplementedException();
}
Now we just need to provide the logic which would be used to identify that the two objects of Employee
are equal or not which for this particular example we will do by checking the name
and department
of the employee
is the same, or we can also add the Gender
if we like as this is just for getting an understanding of how we can implement it so it doesn’t matter much.
After implementing the Equals
method, here is how the method looks like:
public bool Equals(Employee other)
{
var IsEqual = this.Name == other.Name && this.Department == other.Department;
return IsEqual;
}
So the two objects of Employee
will be considered Equal
if they both contain in Name
field and Department
field same value. Our one field is of type String
which does the value equality for == operator and the other is Enum
which is a primitive type in C# so we know that in case of primitive type also == operator checks for value Equality, so both operations will check for value equality which is what we are trying to do here.
Step 2 - Overriding Object.Equals Method
As we are done with the IEquatable<Employee>
part, let’s now override
the Equals
method so that it also returns the same result that we would get when checking for equality via IEquatable<Employee>
, the implementation for which would be:
public override bool Equals(object obj)
{
var IsEqual = false;
if(obj is Employee)
{
IsEqual = Equals((Employee)obj);
}
return IsEqual;
}
What we are doing here is that first we need to make sure that the object that is passed in as parameter is of type Employee
which totally makes sense as Object.Equals
is not type safe and it is possible to pass an object of type Person
instead of Employee
and the compiler will not give any compile time error but the code would fail at run-time in that case, but adding that if
block would save us from breaking the code at run-time and the method will simply return false
which is the correct result as object of type Person
and Employee
can never be equal.
The thing to note here is that this Object.Equals
override will be less efficient than Equals
method implementation of IEquatable<T>
because the former one has cost of Boxing
when it will get called and then we are unboxing it back to Employee
which IEquatable<Employee>
implementation saves us from these.
What we can do is try to always call using the IEquatable<T>
method, if we are concerned about performance and memory cost.
Step 3 - Overloading == and != Operator
Now let’s also implement the ==
and !=
operator for Employee
which would make sure that checking for Equality using these will return consistent result what we were getting using Object.Equals
or IEquatable<Employee> Equals
method. So let’s add the implementation for ==
operator first which is:
public static bool operator ==(Employee employee,Employee otherEmployee)
{
var IsEqual = employee.Equals(otherEmployee);
return IsEqual;
}
The method is pretty simple, it is also reusing the Equals
method implementation that we did for IEquatable<Employee>
, if we build the code, it would not build and will give an error saying that it hasn’t found the implementation of !=
operator, in C# if we overload ==
operator for a type, it is mandatory to provide the implementation for inverse of it, i.e. !=
operator implementation as well, otherwise we will get the following compile time error:
Error CS0216: The operator 'Employee.operator ==(Employee, Employee)' requires a matching operator '!=' to also be defined
If we have overloaded any one of the operators, either ==
or !=
we would need to implement the other one as well, so let’s implement the other one as well:
public static bool operator !=(Employee employee, Employee otherEmployee)
{
var IsNotEqual = !employee.Equals(otherEmployee);
return IsNotEqual;
}
We can see that it is just inverting the result returned the Equals
method of IEquatable<Employee>
and passing it back to the caller. Now if we build our solution again, we will be able to build it successfully without any errors.
Step 4 - Implementing GetHashCode
It is a practice and considered mandatory that if we override the Equals
method for a type, then we must also provide the overridden implementation for GetHashCode()
.
If we peek into the implementation of Object
, we will see that there is a virtual
method present in it named GetHashCode
, its purpose is to return the 32-bit hash of the value which is contained in the object itself.
You might be wondering what is the purpose of this code, the GetHashCode
method is used by types that internally use HashTables
to store the objects which are normally Collections
so they consume this method and get the hash value of the object to be used in the hash table as HashTables
utilize the hash codes of the objects. In Framework Class Libraries, the Dictionary<TKey,TValue>
also uses HashTable
internally.
Hash Codes is a complete topic on which a full article can be written to cover its aspects and we will not be focusing in detail on it. How Hash Tables work is that if two objects return true
when called Equals
method on them, then the Hash Codes for both objects should also be returned same when we call GetHashCode
method on both which in our case would mean that the following line should return true
as well.
So what it actually means is that If <span style="color: #444444"><span style="color: black">employee.Equals(OtherEmployee</span>)</span>
results in true
, then employee.GetHashCode() == otherEmployee.GetHasCode()
should also return true
as result. If the GetHashCode
method is not implemented how it is required, then the using Dictionary
on our type wouldn’t work properly and can cause problems.
Now let’s add the implementation for our Employee
type GetHashCode
method which would be:
public override int GetHashCode()
{
return Name.GetHashCode() ^ Department.GetHashCode();
}
So it is quite simple to understand that what we are doing above, we are just taking the HashCode
for our String
field Name
and Department
Enumeration and combining them using XOR as both the framework provided types so there is already implementation presentation for generating the Hash Code for these types, so we are just reusing the Hash Codes that are already available and provided by framework.
Now let’s add some code in the Main
method to verify what we have implemented is working as expected, add the following code in the Main
and run it:
static void Main(string[] args)
{
Employee ehsan = new Employee("Ehsan Sajjad", Gender.Male, Department.SoftwareDevelopment);
Employee ehsan2 = new Employee("Ehsan Sajjad", Gender.Female, Department.SoftwareDevelopment);
Employee bilal = new Employee("Bilal Asghar", Gender.Male, Department.QualityAssurance);
object ehsanObj = ehsan;
Console.WriteLine(ehsan.Equals(ehsan2));
Console.WriteLine(ehsanObj.Equals(ehsan2));
Console.WriteLine(ehsan == ehsan2);
Console.WriteLine(ehsan.Equals(bilal));
Console.WriteLine(ehsanObj.Equals(bilal));
Console.WriteLine(ehsan == bilal);
Console.ReadKey();
}
The following is the result which was printed on the Console:
We can observe that for ehsan
and ehsan2
objects, all the three conditions have evaluated to return true
which of course means that both are equal which was expected as this is how we defined to check for equality in our type, though we defined Gender
field different in both objects but it has evaluated that both are equal as we are not considering Gender
field when deciding that if the two are equal or not.
Summary
Following are the points that we learned in this post:
- We learned how to provide Custom implementation for Equality checking of Value Types
- Implementing Equality for Value Types is normally good to do as it would improve performance by eliminating boxing/unboxing and reflection cost which can make our code inefficient.
- There are multiple things to take care of when overriding equality for Value Types which includes:
- Implementing
IEquatable<T> interface
of that Value Type which will have type safe Equals
method for checking two object of a type T
- Overriding the
Object.Equals
method which in turn calls the Equals
method which we implemented for IEquatable<T>
- Implementing the
==
and !=
operator overloads which also call the Equals
method on IEquatable<T>
- Implementing
GetHashCode
of Object
for our value type T
- This is the recommended approach for implementing Equality for Value Types and this is not the recommended way in case of Reference Types.
Might Also Like to Read