Override Equals On Overloading

ID

csharp.override_equals_on_overloading

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

equals, reliability

Description

Reports class or struct types that overload operator == or operator != but do not also override Equals(object obj). Without the matching Equals, the == operator and the equality-by-method path disagree for the same operands.

Rationale

== is a static call, dispatched at compile time by operand type, while Equals is a virtual call through System.Object. Collections (Dictionary, HashSet, LINQ) and many framework APIs use Equals. If the two are inconsistent, the same pair of objects will compare equal under one path and unequal under the other — a notoriously hard bug to diagnose.

public class Money                                                     // FLAW
{
    public decimal Amount { get; }

    public static bool operator ==(Money a, Money b) =>
        a is not null && b is not null && a.Amount == b.Amount;

    public static bool operator !=(Money a, Money b) => !(a == b);
}

public class MoneyOk
{
    public decimal Amount { get; }

    public static bool operator ==(MoneyOk a, MoneyOk b) =>            // OK
        a is not null && b is not null && a.Amount == b.Amount;

    public static bool operator !=(MoneyOk a, MoneyOk b) => !(a == b);

    public override bool Equals(object obj) =>
        obj is MoneyOk m && m.Amount == Amount;

    public override int GetHashCode() => Amount.GetHashCode();
}

Remediation

Whenever you overload operator == (or operator !=), also override Equals(object) and GetHashCode(). Have the operator and Equals share the same comparison logic so the two paths return identical results.