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:
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
Equalsshould also overrideGetHashCode. - Handle null explicitly inside
==and!=. - Value-based equality fits immutable value objects better than lifecycle-based entities.
- Prefer clear, consistent semantics over operator trickery.

