Exception In GetHashCode
ID |
csharp.exception_in_gethashcode |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
equals, reliability |
Description
Reports overrides of GetHashCode() that contain a throw statement. GetHashCode is
invoked implicitly by every hash-based collection and LINQ operator; the runtime contract
requires it to be total — it must never throw, even when the object is in an unexpected
state.
Rationale
Dictionary, HashSet, Hashtable, ConcurrentDictionary, and LINQ operators such as
GroupBy and Distinct all call GetHashCode while computing buckets and lookups. A
throw at that point surfaces far from its cause, breaks the
Equals/GetHashCode contract (equal objects must have equal hashes), and can leave
hash-based collections in an inconsistent state where elements seem to disappear.
GetHashCode should treat all reachable object states as valid for hashing — combining
the available immutable fields and returning 0 when no fields are available is far
better than throwing.
public class Item
{
private readonly string id;
public override int GetHashCode()
{
if (id == null)
throw new InvalidOperationException("id missing"); // FLAW
return id.GetHashCode();
}
public override bool Equals(object obj) => obj is Item other && id == other.id;
}
public class ItemOk
{
private readonly string id;
public override int GetHashCode() // OK, never throws
{
return id?.GetHashCode() ?? 0;
}
public override bool Equals(object obj) => obj is ItemOk other && id == other.id;
}