operator overloading
equality operators
programming concepts
object-oriented programming
C# operators

Operator overloading , , Equals

Master System Design with Codemia

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

Introduction

When you overload == and != for a C# type, you are defining what equality means for that type in everyday code. That decision should stay consistent with Equals and GetHashCode, otherwise collections, comparisons, and developer expectations start to conflict. Good equality code is less about syntax tricks and more about a coherent value model.

Why == and Equals must agree

In C#, reference types compare by reference with == unless you overload the operator. Equals also defaults to reference equality unless you override it. If your type behaves like a value object, such as a point, money amount, or date range, you usually want equality to depend on data rather than memory identity.

That means these members should tell the same story:

  • '=='
  • '!='
  • 'Equals(object)'
  • 'GetHashCode()'

If a == b is true but a.Equals(b) is false, the type becomes confusing and error-prone.

A correct value-based implementation

Here is a simple example using a Point2D class:

csharp
1using System;
2
3public sealed class Point2D : IEquatable<Point2D>
4{
5    public int X { get; }
6    public int Y { get; }
7
8    public Point2D(int x, int y)
9    {
10        X = x;
11        Y = y;
12    }
13
14    public bool Equals(Point2D other)
15    {
16        if (ReferenceEquals(other, null))
17            return false;
18
19        return X == other.X && Y == other.Y;
20    }
21
22    public override bool Equals(object obj)
23    {
24        return Equals(obj as Point2D);
25    }
26
27    public override int GetHashCode()
28    {
29        unchecked
30        {
31            return (X * 397) ^ Y;
32        }
33    }
34
35    public static bool operator ==(Point2D left, Point2D right)
36    {
37        if (ReferenceEquals(left, right))
38            return true;
39
40        if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
41            return false;
42
43        return left.Equals(right);
44    }
45
46    public static bool operator !=(Point2D left, Point2D right)
47    {
48        return !(left == right);
49    }
50}
51
52class Program
53{
54    static void Main()
55    {
56        var a = new Point2D(3, 4);
57        var b = new Point2D(3, 4);
58
59        Console.WriteLine(a == b);
60        Console.WriteLine(a.Equals(b));
61    }
62}

This implementation handles null correctly, keeps the operators consistent with Equals, and provides a hash code that matches the equality rule.

Why GetHashCode matters

It is tempting to focus only on == and Equals, but hash-based collections depend on GetHashCode. If two objects are equal, they must return the same hash code. Otherwise, types such as Dictionary<TKey, TValue> and HashSet<T> may behave incorrectly.

That is why overriding Equals without overriding GetHashCode is incomplete. The code may appear to work in simple tests and then fail in real collections later.

When operator overloading is appropriate

Not every class should overload equality operators. Use value-based equality when the object's identity is its data. Good candidates include coordinates, value objects, immutable settings, and small domain types where two instances with the same values should be treated as equal.

Avoid it for entities whose identity comes from lifecycle or persistence identity. For example, two different user records with the same display name are usually not equal.

Common Pitfalls

One common mistake is overloading == but leaving Equals unchanged. That splits the type into two competing definitions of equality and guarantees confusion.

Another issue is forgetting null handling. An overloaded == operator that calls instance methods directly can throw when either side is null. Use ReferenceEquals checks first.

Mutable types are another risk. If the fields used for equality can change after the object is inserted into a hash-based collection, lookups can break because the hash code changes during the object's lifetime.

Finally, do not overload equality only because the language allows it. If the semantics are not obvious to another developer reading the code, you are creating cleverness instead of clarity.

Summary

  • Overloaded equality operators should agree with Equals.
  • Any type that overrides Equals should also override GetHashCode.
  • Handle null explicitly inside == and !=.
  • Value-based equality fits immutable value objects better than lifecycle-based entities.
  • Prefer clear, consistent semantics over operator trickery.

Course illustration
Course illustration

All Rights Reserved.