Override equals and operator equals on value types

Rule ND1313 Design

For value types, the inherited Equals() uses Reflection (expensive) and compares all instance fields (often unnecessary). If instances get compared, sorted or used as hash table keys, implement Equals(), GetHashCode() and the equality/inequality operators.

How to fix

Implement Equals(), GetHashCode() and the equality and inequality operators, or transform the type into a record struct.

Estimated Debt: 15 minutes plus 2 minutes per instance field.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Override equals and operator equals on value types</Name>
// <Id>ND1313:OverrideEqualsAndOperatorEqualsOnValueTypes</Id>

warnif count > 0
from t in JustMyCode.Types where
  t.IsStructure &&
  // Don't warn about C# 15 union types, their struct is generated by the compiler
  !t.IsUnion &&
  t.InstanceFields.Count() > 0
let equalsMethod = t.InstanceMethods.Where(m0 => m0.Name == "Equals(Object)").SingleOrDefault()
where equalsMethod == null

where
   // Don't warn about structure with a fixed field that is not meant to be compared
   !t.Fields.Any(f => f.HasAttribute("System.Runtime.CompilerServices.FixedBufferAttribute".AllowNoMatch())) &&

    // Don't warn about InlineArrayAttribute's field
   !t.HasAttribute("System.Runtime.CompilerServices.InlineArrayAttribute".AllowNoMatch())

select new {
   t,
   t.InstanceFields,
   Debt = (12 + 2*t.InstanceFields.Count()).ToMinutes().ToDebt(),
   Severity = Severity.High
}

//<Expl>
// The *{0}* should override the *Equals()* and *GetHashCode()* methods.
// Additionally, consider implementing the *==* and *!=* operators 
// if you anticipate users comparing or sorting instances or using 
// them as keys in hash tables.
// Alternatively transform it into a *record struct*.
//</Expl>

//<Desc>
// For value types, the inherited *Equals()* uses *Reflection* (expensive)
// and compares all instance fields (often unnecessary). If instances get
// compared, sorted or used as hash table keys, implement *Equals()*,
// *GetHashCode()* and the equality/inequality operators.
//</Desc>

//<HowToFix>
// Implement *Equals()*, *GetHashCode()* and the equality and inequality
// operators, or transform the type into a *record struct*.
//
// Estimated Debt: 15 minutes plus 2 minutes per instance field.
//</HowToFix>

Design: all 16 items