C#
IEquatable
Object.Equals
programming
.NET

What's the difference between IEquatable and just overriding Object.Equals?

Master System Design with Codemia

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

Introduction

In C sharp, both IEquatable<T> and overriding object.Equals define equality behavior, but they target different call paths. Correct implementations typically use both, not one or the other. If equality is incomplete, hash collections, LINQ operations, and comparisons can behave inconsistently.

Role of object.Equals

object.Equals(object) is the universal polymorphic equality method available on all .NET types. Overriding it defines behavior when your instance is compared through non-generic APIs.

csharp
1public sealed class Point
2{
3    public int X { get; }
4    public int Y { get; }
5
6    public Point(int x, int y)
7    {
8        X = x;
9        Y = y;
10    }
11
12    public override bool Equals(object obj)
13    {
14        return obj is Point other && X == other.X && Y == other.Y;
15    }
16
17    public override int GetHashCode() => HashCode.Combine(X, Y);
18}

This is required for compatibility with framework APIs that accept object.

Role of IEquatable<T>

IEquatable<T> adds strongly typed equality for generic contexts. It avoids boxing and repeated runtime type checks.

csharp
1public sealed class Point : IEquatable<Point>
2{
3    public int X { get; }
4    public int Y { get; }
5
6    public Point(int x, int y)
7    {
8        X = x;
9        Y = y;
10    }
11
12    public bool Equals(Point other)
13    {
14        if (ReferenceEquals(other, null)) return false;
15        return X == other.X && Y == other.Y;
16    }
17
18    public override bool Equals(object obj) => Equals(obj as Point);
19    public override int GetHashCode() => HashCode.Combine(X, Y);
20}

Generic collections such as List<T> and HashSet<T> benefit from typed equality.

Why You Usually Need Both

Best practice for value-like equality in reference types:

  1. implement IEquatable<T>
  2. override Equals(object) and forward to typed method
  3. override GetHashCode
  4. optionally overload == and !=

This keeps behavior consistent across generic and non-generic usage.

Hash Code Contract

Equality and hash code must align. If two values are equal, their hash codes must match.

csharp
1var a = new Point(1, 2);
2var b = new Point(1, 2);
3
4Console.WriteLine(a.Equals(b));
5Console.WriteLine(a.GetHashCode() == b.GetHashCode());

Breaking this contract causes unpredictable dictionary and hash set behavior.

Structs and Boxing Considerations

For value types, implementing IEquatable<T> is especially important because it reduces boxing during comparisons.

csharp
1public readonly struct Currency : IEquatable<Currency>
2{
3    public decimal Amount { get; }
4
5    public Currency(decimal amount) => Amount = amount;
6
7    public bool Equals(Currency other) => Amount == other.Amount;
8    public override bool Equals(object obj) => obj is Currency other && Equals(other);
9    public override int GetHashCode() => Amount.GetHashCode();
10}

This helps performance in high-volume comparison paths.

Operator Overloads and Consistency

If you overload == and !=, keep them aligned with Equals.

csharp
public static bool operator ==(Point left, Point right) => Equals(left, right);
public static bool operator !=(Point left, Point right) => !Equals(left, right);

Inconsistent operator and method semantics create confusing bugs.

Entity Versus Value Object Design

Equality semantics depend on domain modeling.

  • value objects usually compare by full value fields
  • entities usually compare by stable identity fields

Choose one rule and apply it consistently. Mixing identity and field equality in one type leads to unpredictable behavior in collections.

Equality Test Suite Recommendations

Add tests for:

  • reflexive behavior
  • symmetry
  • transitivity
  • null handling
  • hash code parity for equal objects

These tests protect equality contracts as types evolve.

Common Pitfalls

A common pitfall is overriding Equals without overriding GetHashCode. Another is implementing IEquatable<T> but forgetting object-level override, causing inconsistent behavior in non-generic paths. Developers also compare mutable fields in hash keys, then mutate after insertion. Operator overloads are often added without contract alignment. Finally, null handling is sometimes omitted in typed equality methods.

Summary

  • 'object.Equals handles non-generic and polymorphic equality.'
  • 'IEquatable<T> provides typed equality for generic performance and clarity.'
  • Implement both for robust behavior across .NET APIs.
  • Keep GetHashCode consistent with equality semantics.
  • Align operator overloads with equality methods.
  • Use tests to preserve equality contracts through refactors.

Course illustration
Course illustration

All Rights Reserved.