Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Overriding Equals and GetHashCode Laconically in C#

0.00/5 (No votes)
9 Aug 2018 1  
In this article, we'll consider overriding the Equals and GetHashCode methods, why IEquatable interface is useful and how Tuples introduced in C# 7.0 can make our implementation laconic.

Introduction

It’s essential to know how to override the Equals and GetHashCode methods to properly define the equality of types we create. In this article, we’ll look at how to implement these methods, why IEquatable interface is useful and how Tuples introduced in C# 7.0 can make our implementation elegant.

Basic Implementation

To understand the importance of Equals and GetHashCode methods, let’s start with reference types and create a simple class Person:

public class Person
{
   public int Id { get; set; }
   public string Name { get; set; }
}

After that, run a simple test:

void Main()
{
   var person1 = new Person { Id = 1, Name = "Eric" };
   var person2 = new Person { Id = 1, Name = "Eric" };

   var personList = new List<Person> { person1 };
   var personDictionary = new Dictionary<Person, int> { { person1, 1 } };

   Console.WriteLine($"person1.Equals(person2) = { person1.Equals(person2) }");
   Console.WriteLine($"personList.Contains(person2) = { personList.Contains(person2) }");
   Console.WriteLine($"personDictionary.ContainsKey(person2) = 
                                 { personDictionary.ContainsKey(person2) }");
}

Note that by default, our Person objects are compared by their reference values. Since person1 and person2 are not the same object and they have different references, they are not considered equal. The result is:

//person1.Equals(person2) = False
//personList.Contains(person2) = False
//personDictionary.ContainsKey(person2) = False

In order to set rules for a proper equality comparison, we need to override the Object.Equals method.
Note: If we just implement a method named Equals without the override keyword, it will just be an ordinary method. It will work for the first line of the test where Equals is called explicitly, but all the other lines will still be returning False. Let’s add the following code to our Person class:

public override bool Equals(object obj)
{
   var person = obj as Person;
   if (person == null)
      return false;
   return person.Id == Id && person.Name == Name;
}

Now, if we run the same test again, we get the result:

//person1.Equals(person2) = True
//personList.Contains(person2) = True
//personDictionary.ContainsKey(person2) = False

Note that the overridden Equals method works well for the List.Contains (2nd line), but Dictionary.ContainsKey still returns False (3rd line). It’s because such containers as HashSet or Dictionary use a hash function to compare objects. If we execute the following code, we will notice that currently person1 and person2 have different hash values:

Console.WriteLine(person1.GetHashCode()); // 43839548
Console.WriteLine(person2.GetHashCode());  // 654235

Let’s override GetHashCode method in the Person class as well:

public override int GetHashCode() =>
   new { Id, Name }.GetHashCode();

Now person1 and person2 have the same hash values (if values of their properties have same values) and Dictionary.ContainsKey is returning True as well!

Value Types and IEquatable

It’s not necessary to override Object.Equals method for value types if we are satisfied by the default comparison logic happening behind the scenes through reflection. However, the reflection is leading to poor performance and it’s recommended to explicitly override Equals method. Note that the Object.Equals accepts object data type as a parameter. It’s not beneficial for value types since boxing occurs when value types are being casted to object and it results in poor performance. That’s where implementation of the IEquatable interface becomes helpful. The interface has only one method: public bool Equals(T other). The difference between Object.Equals and IEquatable.Equals is the data type of the input parameter. IEquatable allows passing a strongly typed parameter and it resolves the problem with boxing which happens when Object.Equals is used.

It’s considered as a good practice to override GetHashCode method along with Equals and it’s better than relying on the implicit logic of this method. Let’s replace the class keyword with struct for the Person and implement IEquatable interface:

public struct Person : IEquatable<Person>
{
   public int Id { get; set; }
   public string Name { get; set; }

   public bool Equals(Person person) =>
      person.Id == Id && person.Name == Name;

   public override int GetHashCode() =>
      new { Id, Name }.GetHashCode();
}

If we run the same test for our structure, we get these results:

//person1.Equals(person2) is True
//personList.Contains(person2) = True
//personDictionary.ContainsKey(person2) = True

Reference types don’t benefit from implementation of IEquatable as much as value types, but some developers still like to implement it for consistency along with overriding Object.Equals method.

Replacing Code with Tuples

Now, let's consider the main point which motivated me to write this article. We can use Tuples introduced in C# 7.0 to replace the tedious pairwise comaprison of each field in the Equals method! We can make it significantly shorter:

public bool Equals(Person person) =>
    (Id, Name).Equals((person.Id, person.Name));

Also, we can modify GetHashCode method:

public override int GetHashCode() => 
    (Id, Name).GetHashCode();

Recap and Final Code

Both, Object.Equals and GetHashCode methods should be overridden for reference types together in order to get correct results of equality comparison. Performance of value types benefits from implementation of IEquatable interface because of the strongly typed parameter its Equals method has. Overriding the GetHashCode method for value types helps explicitly define the hash equality rules and avoid relying on the implicit logic. For consistency, many developers prefer to explicitly override Object.Equals and GetHashCode regardless of type (either value type or reference type); also, they implement IEquatable interface. See the final code of the Person structure/class including overloads for == and != operators:

//public class Person : IEquatable<Person>
public struct Person : IEquatable<Person>
{
    public int Id { get; set; }
    public string Name { get; set; }

    public bool Equals(Person person) =>
        (!object.ReferenceEquals(person, null)) &&
        (Id, Name).Equals((person.Id, person.Name)); // using Tuples

        //person.Id == Id && person.Name == Name;    // or using traditional style

    public override bool Equals(object obj) =>
        (obj is Person) && Equals((Person) obj);

    public override int GetHashCode() =>
        (Id, Name).GetHashCode();

    public static bool operator ==(Person p1, Person p2) =>
        (!object.ReferenceEquals(p1, null)) && p1.Equals(p2);

    public static bool operator !=(Person p1, Person p2) =>
        !(p1 == p2);
}

See the source code on GitHub.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here