C# – How to implement GetHashCode() and Equals()

The simplest way to implement GetHashCode() is to use the built-in System.HashCode.Combine() method and pick the properties you want to include. Let it do the work for you. Furthermore, the simplest way to implement Equals() is to use the is operator and compare all the properties. Here’s an example:

public class Movie
{
    public string Title { get; set; }
    public int Year { get; set; }

    public override int GetHashCode()
    {
        return System.HashCode.Combine(Title, Year);
    }
    public override bool Equals(object? obj)
    {
        return obj is Movie other && Title == other.Title && Year == other.Year;
    }
}
Code language: C# (cs)

Note: Use (Title, Year).GetHashCode() in versions before .NET Core. Add the System.ValueTuple package if necessary.

Now this class can be used with a HashSet<T> and as a Dictionary key. In addition, Equals() can be used to do value-based equality checks (i.e. movie.Equals(other)).

What happens when you don’t implement GetHashCode() / Equals()?

Override and implement GetHashCode() / Equals() when you’re going to use your class with a hashing data structure (i.e. with HashSet<T> or as a Dictionary key).

Why? Well, just take a look at what Microsoft has to say about it:

Do not use the default implementation of this method (GetHashCode()) as a unique object identifier for hashing purposes

Ref: Microsoft – System.Object.GetHashCode() remarks

To illustrate this, consider the following code:

var movies = new HashSet<Movie>()
{
    new Movie() { Title = "Jaws", Year = 1975},
    new Movie() { Title = "Jurassic Park", Year = 1993},
    new Movie() { Title = "Jurassic Park", Year = 1993}
};

Console.WriteLine($"There are {movies.Count} unique movies");
Code language: C# (cs)

When you don’t implement your own GetHashCode(), it uses the default implementation and outputs the following:

There are 3 unique moviesCode language: plaintext (plaintext)

It generated unique hash codes for all of the movie objects, even the two with the same values (Jurassic Park, 1993), and kept all of them in the HashSet.

When you override and implement your own GetHashCode() (and Equals()), like the one shown at the top, it outputs the following:

There are 2 unique moviesCode language: plaintext (plaintext)

It generated the same hash code for the two movie objects with the same values (Jurassic Park, 1993). One of them was de-duped by the HashSet itself.

Use IEqualityComparer<T> when you don’t want to change the class

Let’s say you want to use a class with a HashSet, but you don’t want to change the class by overriding and implementing GetHashCode() and Equals(). Or perhaps you don’t control the class and can’t change it.

Instead, you can implement GetHashCode() and Equals() for the class externally in IEqualityComparer<T>, like this:

using System.Collections.Generic;

public class CompareMovies : IEqualityComparer<Movie>
{
    public bool Equals(Movie? left, Movie? right)
    {
        return left?.Title == right?.Title
            && left?.Year == right?.Year;
    }

    public int GetHashCode(Movie m)
    {
        return System.HashCode.Combine(m.Title, m.Year);
    }
}
Code language: C# (cs)

Then pass it in to the HashSet (or Dictionary) constructor, like this:

var movies = new HashSet<Movie>(new CompareMovies())
{
    new Movie() { Title = "Jaws", Year = 1975},
    new Movie() { Title = "Jurassic Park", Year = 1993},
    new Movie() { Title = "Jurassic Park", Year = 1993}
};

Console.WriteLine($"There are {movies.Count} unique movies");
Code language: C# (cs)

This outputs the following, showing that it used the hashing methods in the CompareMovies object:

There are 2 unique moviesCode language: plaintext (plaintext)

Leave a Comment