Equals HashCode Override

ID

csharp.equals_hashcode_override

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

equals, reliability

Description

Reports types that override exactly one of Equals(object) and GetHashCode() instead of both. The .NET equality contract requires the two methods to remain consistent: two values that compare equal must produce the same hash code.

Rationale

Hash-based collections — Dictionary<TKey, TValue>, HashSet<T>, ConcurrentDictionary, LINQ operators such as Distinct, GroupBy, Union — bucket items by GetHashCode and then disambiguate within a bucket with Equals. Overriding only one half silently breaks those collections: equal objects can hash to different buckets and never be found, or unequal objects can collide and be wrongly merged.

public class MoneyBad                                           // FLAW — only Equals overridden
{
    public decimal Amount { get; }

    public override bool Equals(object obj) =>
        obj is MoneyBad other && Amount == other.Amount;
}

public class MoneyOk
{
    public decimal Amount { get; }

    public override bool Equals(object obj) =>                  // OK — both overridden together
        obj is MoneyOk other && Amount == other.Amount;

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

Remediation

Override Equals(object) and GetHashCode() as a pair. Derive the hash from the same fields you compare in Equals and never include mutable state that changes after the object is placed in a hashed collection. For value-like types, prefer C# records, which generate consistent Equals/GetHashCode automatically.