Question
In C#, consider the following class:
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which implementation is correct?
return base.GetHashCode();
// or:
// return this.FooId.GetHashCode();
}
}
Equals has been overridden because Foo represents a row in a Foos table, and two Foo objects should be considered equal when they have the same FooId.
Which GetHashCode implementation is preferred in this case, and why is it important to override GetHashCode when Equals is overridden?
Short Answer
By the end of this page, you will understand the contract between Equals and GetHashCode in C#, why hash-based collections depend on both methods, and how to correctly implement value-based equality for a class like Foo. You will also learn common mistakes, practical usage patterns, and how to avoid subtle bugs in Dictionary, HashSet, and similar types.
Concept
In C#, Equals answers the question: Are these two objects logically the same?
GetHashCode answers a different question: Which hash bucket should this object go into?
These two methods are tightly connected.
The rule you must follow
If two objects are equal according to Equals, they must return the same hash code from GetHashCode.
In other words:
a.Equals(b) == trueimpliesa.GetHashCode() == b.GetHashCode()- The reverse is not required:
- Two objects can have the same hash code and still not be equal
Why this matters
Many .NET collections use hash codes internally, including:
Dictionary<TKey, TValue>HashSet<T>ConcurrentDictionary<TKey, TValue>Lookup<TKey, TElement>
These collections first use GetHashCode to find a bucket, then use Equals to confirm the match.
Mental Model
Think of a hash-based collection like a library with numbered shelves.
GetHashCodechooses the shelfEqualschecks whether the book on that shelf is the exact one you want
If two books represent the same thing, they must go to the same shelf. Otherwise, the librarian looks on the wrong shelf and says, "It is not here," even though the matching book exists elsewhere.
So:
GetHashCodeis the location hintEqualsis the final identity check
If those two systems disagree, the collection becomes unreliable.
Syntax and Examples
Basic pattern
When you override Equals, override GetHashCode using the same fields.
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo other = obj as Foo;
if (other == null)
{
return false;
}
return FooId == other.FooId;
}
public override int GetHashCode()
{
return FooId.GetHashCode();
}
}
Example: equal objects must have equal hash codes
Foo a = new Foo { FooId = 10, FooName = "Alpha" };
Foo b = Foo { FooId = , FooName = };
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.GetHashCode());
Console.WriteLine(b.GetHashCode());
Step by Step Execution
Consider this code:
public class Foo
{
public int FooId { get; set; }
public override bool Equals(object obj)
{
Foo other = obj as Foo;
if (other == null)
return false;
return FooId == other.FooId;
}
public override int GetHashCode()
{
return FooId.GetHashCode();
}
}
var x = new Foo { FooId = 5 };
var y = new Foo { FooId = 5 };
var set = new HashSet<Foo>();
set.Add(x);
Console.WriteLine(set.Contains(y));
What happens step by step
xis created withFooId = 5.
Real World Use Cases
1. Entity objects identified by an ID
If two objects represent the same database row, equality is often based on the primary key.
new Foo { FooId = 7, FooName = "A" }
new Foo { FooId = 7, FooName = "B" }
These may be considered equal because they refer to the same entity.
2. Preventing duplicates in a HashSet
var foos = new HashSet<Foo>();
foos.Add(new Foo { FooId = 1, FooName = "One" });
foos.Add(new Foo { FooId = 1, FooName = "Duplicate" });
With correct equality and hash code logic, the second item is treated as a duplicate.
3. Using custom objects as dictionary keys
var cache = new Dictionary<Foo, string>();
cache[new Foo { FooId = 3 }] = "cached value";
string value = cache[new Foo { FooId = 3 }];
This only works reliably if both and are implemented consistently.
Real Codebase Usage
In real projects, developers often use equality in a few common ways.
Identity-based entities
For database-backed domain models, equality is sometimes based on a stable identifier such as Id.
return FooId == other.FooId;
This is common when objects may be loaded from different places but still represent the same record.
Guard clauses in Equals
Developers usually write fast checks first:
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
These improve readability and avoid unnecessary work.
Keeping equality and hashing aligned
A common rule in teams is:
- If a field participates in
Equals, it must also participate inGetHashCode - If a field does not participate in
Equals, it should usually not participate inGetHashCode
Validation and immutability concerns
Common Mistakes
1. Overriding Equals but not GetHashCode
This is the classic mistake.
Broken example
public override bool Equals(object obj)
{
Foo other = obj as Foo;
return other != null && FooId == other.FooId;
}
If GetHashCode is not updated, hash-based collections may behave incorrectly.
2. Using base.GetHashCode() with custom equality
Broken example
public override int GetHashCode()
{
return base.GetHashCode();
}
Why it is wrong:
base.GetHashCode()is usually based on reference identity- Your
Equalsis based onFooId
Comparisons
| Concept | Purpose | Must match equality fields? | Common use |
|---|---|---|---|
Equals | Determines logical equality | Yes | Comparing objects |
GetHashCode | Helps place objects in hash buckets | Yes | Dictionary, HashSet |
== operator | Operator-based comparison | Not automatically | Convenience syntax |
ReferenceEquals | Checks if two references point to the same object | No | Identity checks |
base.GetHashCode() vs field-based hash code
Cheat Sheet
Quick rules
- If you override
Equals, also overrideGetHashCode - Equal objects must have equal hash codes
- Use the same fields in both methods
- Do not use
base.GetHashCode()when equality is custom - Prefer stable, immutable fields for hashing
Good implementation for the Foo example
public override bool Equals(object obj)
{
return obj is Foo other && FooId == other.FooId;
}
public override int GetHashCode()
{
return FooId.GetHashCode();
}
Better pattern with IEquatable<T>
public class Foo : IEquatable<Foo>
{
public int FooId { get; ; }
{
!(other ) && FooId == other.FooId;
}
{
Equals(obj Foo);
}
{
FooId.GetHashCode();
}
}
FAQ
Why do I need to override GetHashCode if Equals already works?
Because hash-based collections use GetHashCode to find where to look. If equal objects return different hash codes, lookups can fail.
Is base.GetHashCode() ever correct?
Yes, if your equality is also based on reference identity. It is not correct when you define custom logical equality, such as comparing FooId.
Should GetHashCode use all properties?
Only the properties that participate in equality. If Equals only checks FooId, GetHashCode should also only use FooId.
Can two different objects have the same hash code?
Yes. That is allowed. Hash codes are not unique identifiers.
What happens if I change FooId after adding an object to a HashSet?
The object may become hard to find or remove because its hash code changes after insertion.
Should entity equality be based on database ID?
Often yes, especially when the ID is stable and uniquely identifies the entity. But this depends on the domain model and lifecycle of the entity.
Mini Project
Description
Create a small C# program that stores Foo objects in a HashSet<Foo> and demonstrates why matching Equals and GetHashCode matters. This project helps you see the difference between correct and incorrect implementations in a practical way.
Goal
Build a console app where Foo objects with the same FooId are treated as the same logical item inside a HashSet<Foo>.
Requirements
- Create a
Fooclass withFooIdandFooNameproperties. - Override
Equalsso that twoFooobjects are equal when theirFooIdvalues match. - Override
GetHashCodeusing the same field used byEquals. - Add multiple
Fooobjects to a , including duplicates by .
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
C# Type Checking Explained: typeof vs GetType() vs is
Learn when to use typeof, GetType(), and is in C#. Understand exact type checks, inheritance, and safe type testing clearly.
C# Version Numbers Explained: C# vs .NET Framework and Why “C# 3.5” Is Incorrect
Learn the correct C# version numbers, how they map to .NET releases, and why terms like C# 3.5 are inaccurate and confusing.