Programming
C# language
GetHashCode Method
Equals Method
Object Oriented Programming

Why is it important to override GetHashCode when Equals method is overridden?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When you override Equals in C#, you must also override GetHashCode to maintain the contract that equal objects produce the same hash code. Hash-based collections like Dictionary<TKey, TValue>, HashSet<T>, and Hashtable use GetHashCode() to determine which bucket an object belongs to before calling Equals() to confirm a match. If two objects are equal by Equals() but return different hash codes, the collection places them in different buckets and treats them as distinct — causing lookups, containment checks, and deduplication to fail silently.

The Contract

The .NET documentation states two rules:

  1. If a.Equals(b) returns true, then a.GetHashCode() must equal b.GetHashCode()
  2. The reverse is not required — objects with the same hash code may not be equal (hash collisions are allowed)
csharp
1public class Point
2{
3    public int X { get; }
4    public int Y { get; }
5
6    public Point(int x, int y) { X = x; Y = y; }
7
8    public override bool Equals(object obj)
9    {
10        return obj is Point other && X == other.X && Y == other.Y;
11    }
12
13    // WITHOUT overriding GetHashCode — uses default (reference-based)
14}

What Goes Wrong Without GetHashCode

csharp
1var point1 = new Point(1, 2);
2var point2 = new Point(1, 2);
3
4// Equals works correctly
5Console.WriteLine(point1.Equals(point2));  // True
6
7// But hash codes differ (default uses object reference)
8Console.WriteLine(point1.GetHashCode());  // e.g., 46104728
9Console.WriteLine(point2.GetHashCode());  // e.g., 12345678
10
11// Dictionary fails
12var dict = new Dictionary<Point, string>();
13dict[point1] = "First";
14Console.WriteLine(dict.ContainsKey(point2));  // False! Should be True
15
16// HashSet fails
17var set = new HashSet<Point> { point1 };
18Console.WriteLine(set.Contains(point2));  // False! Should be True
19
20// Both point1 and point2 are added as "different" items
21set.Add(point2);
22Console.WriteLine(set.Count);  // 2 — should be 1

The Dictionary and HashSet first compute GetHashCode() to find the bucket, then call Equals() only on items in that bucket. Since point1 and point2 have different hash codes, they go to different buckets, and Equals() is never called.

Correct Implementation

csharp
1public class Point
2{
3    public int X { get; }
4    public int Y { get; }
5
6    public Point(int x, int y) { X = x; Y = y; }
7
8    public override bool Equals(object obj)
9    {
10        return obj is Point other && X == other.X && Y == other.Y;
11    }
12
13    public override int GetHashCode()
14    {
15        return HashCode.Combine(X, Y);
16    }
17}
18
19// Now everything works
20var dict = new Dictionary<Point, string>();
21dict[new Point(1, 2)] = "First";
22Console.WriteLine(dict.ContainsKey(new Point(1, 2)));  // True
23
24var set = new HashSet<Point> { new Point(1, 2) };
25set.Add(new Point(1, 2));
26Console.WriteLine(set.Count);  // 1

How to Implement GetHashCode

csharp
1public override int GetHashCode()
2{
3    return HashCode.Combine(FirstName, LastName, Age);
4}

Using HashCode.Add for Many Fields

csharp
1public override int GetHashCode()
2{
3    var hash = new HashCode();
4    hash.Add(FirstName);
5    hash.Add(LastName);
6    hash.Add(Age);
7    hash.Add(Email);
8    hash.Add(Department);
9    return hash.ToHashCode();
10}

Manual Implementation (Older .NET)

csharp
1public override int GetHashCode()
2{
3    unchecked
4    {
5        int hash = 17;
6        hash = hash * 31 + (FirstName?.GetHashCode() ?? 0);
7        hash = hash * 31 + (LastName?.GetHashCode() ?? 0);
8        hash = hash * 31 + Age.GetHashCode();
9        return hash;
10    }
11}

The unchecked block allows integer overflow without throwing, and the prime multipliers (17, 31) help distribute hash codes evenly across buckets.

Records: Automatic Implementation

C# 9 records generate both Equals and GetHashCode automatically:

csharp
1public record Point(int X, int Y);
2
3var p1 = new Point(1, 2);
4var p2 = new Point(1, 2);
5
6Console.WriteLine(p1.Equals(p2));                     // True
7Console.WriteLine(p1.GetHashCode() == p2.GetHashCode()); // True
8
9var set = new HashSet<Point> { p1 };
10Console.WriteLine(set.Contains(p2));  // True

Records implement value-based equality by default, with correctly paired Equals and GetHashCode.

What Fields to Include

Include the same fields in GetHashCode that you use in Equals:

csharp
1public class Employee
2{
3    public int Id { get; set; }
4    public string Name { get; set; }
5    public string Department { get; set; }
6
7    // Equals based on Id only
8    public override bool Equals(object obj)
9    {
10        return obj is Employee other && Id == other.Id;
11    }
12
13    // GetHashCode must also use only Id
14    public override int GetHashCode()
15    {
16        return Id.GetHashCode();
17    }
18}

If Equals uses Id and Name but GetHashCode only uses Id, the contract is still satisfied (equal objects still produce equal hash codes). But if GetHashCode uses a field that Equals does not, the contract can break.

Common Pitfalls

  • Overriding Equals without GetHashCode: The compiler emits warning CS0659 for this exact situation. Hash-based collections silently fail — lookups return false, duplicate entries appear in sets, and dictionary keys become unreachable.
  • Including mutable fields in GetHashCode: If a field used in GetHashCode changes while the object is in a Dictionary or HashSet, the hash code changes but the object stays in the old bucket. The object becomes unfindable. Use only immutable fields, or do not mutate objects while they are in hash-based collections.
  • Returning a constant hash code: return 0; satisfies the contract but forces every object into the same bucket, turning O(1) lookups into O(n) linear scans. Hash codes should distribute evenly.
  • Using different fields in Equals and GetHashCode: If Equals compares fields A and B but GetHashCode uses fields A and C, two objects that are equal (same A and B, different C) may produce different hash codes, breaking the contract.
  • Forgetting null checks for reference fields: field.GetHashCode() throws NullReferenceException if field is null. Use field?.GetHashCode() ?? 0 or HashCode.Combine() which handles nulls automatically.

Summary

  • If Equals returns true, GetHashCode must return the same value for both objects
  • Hash-based collections (Dictionary, HashSet) use GetHashCode to find buckets before calling Equals
  • Use HashCode.Combine() for a correct, well-distributed implementation
  • Include exactly the same fields in GetHashCode that you use in Equals
  • Never include mutable fields in GetHashCode if objects are stored in hash-based collections
  • C# records generate correct Equals and GetHashCode automatically

Course illustration
Course illustration

All Rights Reserved.