Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C# 7.0
Tip/Trick

Overriding Equals and GetHashCode Laconically in C#

Rate me:
Please Sign up or sign in to vote.
4.80/5 (14 votes)
9 Aug 2018CPOL3 min read 31.9K   11   9
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:

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

After that, run a simple test:

C#
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:

C#
//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:

C#
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:

C#
//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:

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

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

C#
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:

C#
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:

C#
//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:

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

Also, we can modify GetHashCode method:

C#
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:

C#
//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)


Written By
Software Developer (Senior)
United States United States
I, Nick, am a Senior 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.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Hyland Computer Systems14-Aug-18 6:44
Hyland Computer Systems14-Aug-18 6:44 
BugAny object participating in GetHashCode() must be immutable, else... Pin
Huh? Come Again?13-Aug-18 8:49
Huh? Come Again?13-Aug-18 8:49 
PraiseRe: Any object participating in GetHashCode() must be immutable, else... Pin
Nikolai Basov13-Aug-18 16:04
Nikolai 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
Nikolai 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 
The code fix provided in VS 2017 has already taken account of the NullReferenceException.
It generates the code like the following:
C#
class A : IEquatable<A>
{
	public string X, Y;

	public override bool Equals(object obj) {
		return Equals(obj as A);
	}

	public bool Equals(A other) {
		return other != null &&
				X == other.X &&
				Y == other.Y;
	}

	public override int GetHashCode() {
		var hashCode = 1861411795;
		hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(X);
		hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Y);
		return hashCode;
	}

	public static bool operator ==(A a1, A a2) {
		return EqualityComparer<A>.Default.Equals(a1, a2);
	}

	public static bool operator !=(A a1, A a2) {
		return !(a1 == a2);
	}
}

GeneralRe: We can use builtin code fix in VS 2017 Pin
Nikolai Basov16-Aug-18 14:47
Nikolai Basov16-Aug-18 14:47 
GeneralMy vote of 5 Pin
LightTempler10-Aug-18 19:38
LightTempler10-Aug-18 19:38 
GeneralRe: My vote of 5 Pin
Nikolai Basov11-Aug-18 8:08
Nikolai 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.