Click here to Skip to main content
13,733,057 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

5.2K views
9 bookmarked
Posted 9 Aug 2018
Licenced CPOL

Overriding Equals and GetHashCode Laconically in C#

, 9 Aug 2018
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nikolai Basov
Software Developer
United States United States
I, Nick, am a Full Stack Software Engineer and Microsoft Certified Database professional (MCSA) who is passionate about the development of high-quality software, troubleshooting, optimization, Business Intelligence solutions and databases. I’ve been writing software in financial, staffing, consulting, educational, pharmaceutical and other industries.

To stay up to date with my latest articles, read my blog https://www.highgoup.com and connect with me via social networks.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Hyland Computer Systems14-Aug-18 6:44
memberHyland Computer Systems14-Aug-18 6:44 
BugAny object participating in GetHashCode() must be immutable, else... Pin
Huh? Come Again?13-Aug-18 8:49
memberHuh? Come Again?13-Aug-18 8:49 
PraiseRe: Any object participating in GetHashCode() must be immutable, else... Pin
Nikolai Basov13-Aug-18 16:04
memberNikolai Basov13-Aug-18 16:04 
AnswerWe can use builtin code fix in VS 2017 Pin
wmjordan12-Aug-18 23:53
professionalwmjordan12-Aug-18 23:53 
SuggestionRe: We can use builtin code fix in VS 2017 Pin
Nikolai Basov13-Aug-18 15:47
memberNikolai Basov13-Aug-18 15:47 
GeneralRe: We can use builtin code fix in VS 2017 Pin
wmjordan15-Aug-18 23:28
professionalwmjordan15-Aug-18 23:28 
GeneralRe: We can use builtin code fix in VS 2017 Pin
Nikolai Basov16-Aug-18 14:47
memberNikolai Basov16-Aug-18 14:47 
GeneralMy vote of 5 Pin
LightTempler10-Aug-18 19:38
memberLightTempler10-Aug-18 19:38 
GeneralRe: My vote of 5 Pin
Nikolai Basov11-Aug-18 8:08
memberNikolai Basov11-Aug-18 8:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180920.1 | Last Updated 10 Aug 2018
Article Copyright 2018 by Nikolai Basov
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid