Introduction
In C#, if we want to compare two variables, then we can use the == operator. We can also use the Equals method for that, but if we want to compare two reference types, then we need to do something different. This is because in value types, we can compare variables by their value, but in the case of complex type objects that are reference types, the types may contain many fields or properties. We need to define something by which we can compare the property or member values. One method uses reflection, but that method is a bit heavy, and the performance of the application will suffer. However, if a reference of two objects is the same, then we can compare them using the == operator.
If the reference of two complex types is the same, then they are equal, but sometimes we encounter a scenario where the reference of two objects is different, but still, we need to compare types based on their fields and properties.
Difference between == and Equals Method in C#
== compares the reference equality, whereas the Equals method compares only the content. The == operator does not throw an error with null values, whereas the Equals method throws an error if the first value to compare is null. However, we can override the Equals method and change its functionality depending on our requirements so that we are able to compare two complex types.
In the following example, I have created a class Student in which I have overridden the Equals method and the GetHashCode method.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
Student student = (Student)obj;
return (this.Id == student.Id && this.Name == student.Name);
}
public override int GetHashCode()
{
return this.Id.GetHashCode() ^ this.Name.GetHashCode();
}
}
Now if we create two instances of the preceding class.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
if (obj is Student student)
{
return (this.Id == student.Id && this.Name == student.Name);
}
return false;
}
public override int GetHashCode()
{
return this.Id.GetHashCode() ^ this.Name.GetHashCode();
}
public static bool operator ==(Student left, Student right)
{
if (ReferenceEquals(left, null))
{
return ReferenceEquals(right, null);
}
return left.Equals(right);
}
public static bool operator !=(Student left, Student right)
{
return !(left == right);
}
}
The fourth line returns true because we have overridden the method to compare the properties of the class. If we have not implemented these methods, then the fourth line will return false.
Note. It is important to override the GetHashCode method because if our object (of the student class) is a key of Dictionary or Hashtable (that calculates the HashCode of the key), then in that case the Equals method will not provide expected results.
IEquatable vs. IComparable interface in C#
IEquatable and IComparable are interfaces that are used to compare complex type objects. The IEquatable interface is generally used for checking the equality of two complex types, whereas the IComparable interface is used for comparing two objects, and it tells us whether the first object is greater than or less than the other. IEquatable is generally used in cases where we want to compare two objects whereas IComparable is used in the case of the sorting of a list of objects.
Using IEquatable Interface
The IEquatable interface is used to compare two objects by providing the Equals method. It is similar to the Equals method of the Object class (that we have discussed in the preceding). The only difference between the two is that the Equals method of The IEquatable interface is a generic method, and it avoids the boxing and unboxing of objects that improve the performance.
class Employee : IEquatable<Employee>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(Employee other)
{
return (this.Id == other.Id && this.Name == other.Name);
}
}
Note. When implementing the IEquatable interface, it is suggested that we should also override the Equals method of the object class since any user can also call the Equals method by passing an object type. For example, if we create two instances of an Employee class and compare two objects.
Employee ob1 = new Employee();
Employee ob2 = new Employee();
Console.WriteLine(ob1.Equals(ob2));
Then the code above will return true since the properties of both objects have the same value. But if we create objects as in the following.
Employee a = new Employee();
object b = new Employee();
Console.WriteLine(a.Equals((Employee)b)); // Cast 'b' to Employee before using Equals
Then we will get unexpected results because, in this case, our Equals method is not called. So the better approach is to override the Equals method of the Object class when implementing the IEquatable interface.
class Employee: IEquatable<Employee>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(Employee other)
{
return (this.Id == other.Id && this.Name == other.Name);
}
public override bool Equals(object obj)
{
Employee employee = (Employee)obj;
return (this.Id == employee.Id && this.Name == employee.Name);
}
public override int GetHashCode()
{
return this.Id.GetHashCode() ^ this.Name.GetHashCode();
}
}
Using IComparable Interface
The IComparable interface is also used to compare two objects by giving the CompareTo method that returns an integer. If the returned value is 0, then it means two objects are equal otherwise, it will return 1 if the first object is greater than the second and -1 if the first object is smaller than the second.
class PermanentEmployee: IComparable<PermanentEmployee>
{
public int Id { get; set; }
public string Name { get; set; }
public int CompareTo(PermanentEmployee other)
{
return this.Id.CompareTo(other.Id);
}
}
Note. If we do not implement this interface and if we build a List of type PermanentEmployee and call its Sort method, then it will throw an error because the compiler does not know how to sort a PermanentEmployee type of object. However after implementing the IComparable interface, we can sort our complex object list.
The IComparable interface has two versions, simple and generic. The generic version is preferred because it eliminates the necessity of boxing and unboxing, which affects performance.