Record should be immutable

Rule ND1913 Immutability

C#9 record is meant for value-based semantic: value-based equality and immutability, explained here: https://blog.ndepend.com/c9-records-immutable-classes/

A property setter not declared init still allows mutable state, which disrupts the value-based semantics: mutating a record used as a hash table key can corrupt the table, and equality results become unexpected — bugs difficult to spot and fix.

How to fix

Make the record immutable: transform mutable property setters (culpritSetters) into init property initializers, and refactor their callers (methodsCallingCulpritSetters) to stop changing record states.

Estimated Debt: 8 minutes plus 3 minutes per mutable setter and per method calling one.

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>Record should be immutable</Name>
// <Id>ND1913:RecordClassesShouldBeImmutable</Id>

warnif count > 0

from t in Application.Types
where t.IsRecord && 
       // Don't match record struct because when declared
       // with primary constructor it is not immutable
       t.IsClass &&  
      !t.IsImmutable
let culpritSetters = t.InstanceMethods.Where(
  m => m.IsPropertySetter && !m.IsPropertyInit)

let methodsCallingCulpritSetters = culpritSetters.SelectMany(
   m => m.MethodsCallingMe.Where(mc => mc.ParentType != t) )

select new { t, 
   culpritSetters,
   methodsCallingCulpritSetters ,
   Debt = (8 + 3*methodsCallingCulpritSetters.Count()
             + 3*methodsCallingCulpritSetters.Count())
          .ToMinutes().ToDebt(),
   Severity = Severity.High 
}

//<Expl>
// The *record {0}* is mutable because of the *{1}*.
// It is recommended to refrain from including mutable states
// in a record as it disrupts the *value-based semantics*.
//</Expl>

//<Desc>
// C#9 **record** is meant for **value-based semantic**: **value-based
// equality** and **immutability**, explained here:
// https://blog.ndepend.com/c9-records-immutable-classes/
//
// A property setter not declared *init* still allows mutable state, which
// disrupts the value-based semantics: mutating a record used as a hash
// table key can corrupt the table, and equality results become unexpected —
// bugs difficult to spot and fix.
//</Desc>

//<HowToFix>
// Make the record immutable: transform mutable property setters
// (*culpritSetters*) into *init* property initializers, and refactor their
// callers (*methodsCallingCulpritSetters*) to stop changing record states.
//
// Estimated Debt: 8 minutes plus 3 minutes per mutable setter and per
// method calling one.
//</HowToFix>

Immutability: all 15 items