LogIn
I don't have account.

What Happens If You Override Equals() but Not GetHashCode() in C#

Shubham Verma
19 Views

When working with collections like HashSet or Dictionary<TKey, TValue>, understanding the relationship between Equals() and GetHashCode() is critical. Many hidden bugs and performance problems in .NET apps happen because these two methods are used incorrectly.

Let’s unpack this in detail step by step.

The Role of Equals() and GetHashCode()

Every object in C# inherits two fundamental methods from System.Object:

  • public virtual bool Equals(object obj);
  • public virtual int GetHashCode();

They work hand-in-hand to determine object equality and hash identity.

  • Equals() defines logical equality — when two objects mean the same thing.

  • GetHashCode() provides a hash used for fast lookups in hash-based collections (like HashSet, Dictionary and ConcurrentDictionary).

How Hash-Based Collections Work

Collections like HashSet and Dictionary<TKey, TValue> are hash-based. They use a two-step process for lookup and storage:

Hashing Phase:

  • Call GetHashCode() to compute an integer hash.
  • This determines which bucket an object belongs to.

Equality Phase:

  • Once inside the right bucket, call Equals() to check if the item is actually equal to an existing entry.

Example: HashSet Internals (Simplified)

Imagine a HashSet as multiple buckets (like small lists):


Bucket 0: [ ]
Bucket 1: [ ]
Bucket 2: [Person("Alice")]
Bucket 3: [ ]
Bucket 4: [Person("Bob")]

When you add new Person("Alice"):

  • The hash code decides which bucket to use.

  • Then Equals() ensures no duplicates exist in that bucket.

If GetHashCode() returns different values for logically equal objects, they end up in different buckets and Equals() never even gets called to compare them.

This is the Problem when Overriding Equals() but Not GetHashCode()

Consider this class:


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

    public override bool Equals(object obj)
    {
        return obj is Person other && Name == other.Name;
    }

    // ❌ Forgot to override GetHashCode()
}

Let’s test it:


var set = new HashSet<Person>();
set.Add(new Person { Name = "Dev" });
set.Add(new Person { Name = "Dev" });

Console.WriteLine(set.Count);  // Output: 2

Even though both objects are logically equal (Equals() returns true), HashSet stores both, because they have different hash codes.

Why It Happens

By default, GetHashCode() (from object) uses the object's reference in memory. So, two different instances even with identical content, produce different hash codes.

Since the hash codes are different, the objects might end up in different buckets (bucketIndex = hashCode % capacity). In that case, the HashSet won’t even call Equals() to compare them. It only calls Equals() if both objects fall into the same bucket.

The Object Equality Contract

Microsoft’s official guidelines define a clear contract between Equals() and GetHashCode():

If two objects are equal (according to Equals()),
they must return the same value from GetHashCode().

Violating this rule leads to unpredictable behavior in any hash-based collection.

The Rules Summarized

Rule Description
Reflexive x.Equals(x) must be true.
Symmetric x.Equals(y) must equal y.Equals(x).
Transitive If x.Equals(y) and y.Equals(z), then x.Equals(z) must be true.
Consistency Repeated calls must return the same result unless the object changes.
Hash Code Contract If x.Equals(y) is true, then x.GetHashCode() == y.GetHashCode() must be true.

.

Correct Implementation Example

Here’s how to fix it properly:


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

    public override bool Equals(object obj)
    {
        return obj is Person other && Name == other.Name;
    }

    public override int GetHashCode()
    {
        // Null-safe hash computation
        return Name?.GetHashCode() ?? 0;
    }
}

Now test again:


var set = new HashSet<Person>();

set.Add(new Person { Name = "Dev" });
set.Add(new Person { Name = "Dev" });

Console.WriteLine(set.Count);  // ✅ Output: 1

Now Equals() and GetHashCode() are consistent both objects hash to the same bucket and the duplicate is detected.

Real-World Impact

Here’s what can break if you don’t override both:

Collection Problem
HashSet Stores duplicates that should be equal.
Dictionary<TKey, TValue> Treats equal keys as distinct, causing overwriting or lookup failures.
ConcurrentDictionary<TKey, TValue> Thread-safe version suffers the same issue.
LINQ Some LINQ methods (like Distinct()) rely on hash codes and may misbehave.
Contains(), Remove() Fail to find existing items that are considered “equal.”

Performance Implications

Even if things seem to work, a broken hash implementation can:

  • Dramatically increase lookup time (because objects go into wrong buckets)

  • Cause memory waste (duplicate entries)

  • Lead to unpredictable behavior across different .NET versions or runtime optimizations

  • Hash-based collections rely on hash uniformity for performance — mismatched hashes kill that.

How to Implement GetHashCode() Safely

A robust hash code implementation should:

  • Use immutable fields (values that don’t change after construction)

  • Combine multiple fields using a reliable hash combiner

  • Avoid overflow or randomization issues

Example:


class Employee
{
    public int Id { get; set; }
    public string Department { get; set; }

    public override bool Equals(object obj)
    {
        return obj is Employee e && Id == e.Id && Department == e.Department;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Id, Department);
    }
}

.NET’s HashCode.Combine() (C# 8+) simplifies safe, high-quality hash generation.

Common Misconceptions

Myth Reality
"Equals() is enough to define equality." Not for hash-based collections - you need consistent hashes.
"GetHashCode() must be unique." No , only consistent. Collisions are okay; inconsistency isn’t.
"You must override Equals() and GetHashCode() together." Always. They’re inseparable for logical equality.
"Changing hash logic later is safe." Not for persisted or serialized collections. it may corrupt lookups.

Summary

Concept Explanation
Equals() Defines logical equality between objects.
GetHashCode() Determines hash identity for collections.
If only Equals() is overridden Hash-based collections misbehave (duplicates, failed lookups).
If both are overridden correctly Consistent, predictable equality semantics.
Golden Rule Equal objects must have equal hash codes.

In Short

If you override Equals(), you must override GetHashCode().

Failing to do so may not crash your program, but it will silently break correctness in ways that are hard to debug.

Example Summary in One Line

HashSet won’t even check equality between two objects unless their hash codes match.

Responses (1)

Write a response

CommentHide Comments
~ Raj Verma⏱️04 November 2025

Great insight article! It really helps to understand how things like dictionaries and sets might behave unexpectedly. you have explained clearly. it’s important, when working in C# to always override GetHashCode() if you override Equals().

Thanks for sharing!