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.