Classes that are candidate to be turned into structures

Rule ND1304 Design

Structures suit lightweight values (like Int32 or Boolean): as values, they are not managed by the garbage collector, cheaper for plenty of short lived instances. This rule matches classes looking like lightweight values:

- It has instance fields, all typed with value-types.

- It is immutable.

- It implements no interfaces.

- It has no parameterless constructor, is not generic.

- It has no derived classes and derives directly from System.Object.

- Not an ASP.NETCore ViewModel (name ending with Model) or Repository.

Whether instances are numerous short-lived objects is not checked: these criteria are indications, only you can decide if the change is performance wise.

How to fix

Use the keyword struct instead of class.

CAUTION: first understand the deep implications explained here: https://blog.ndepend.com/class-vs-struct-in-c-making-informed-choices/

Estimated Debt: 5 minutes per class plus 1 minute per method using it.

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>Classes that are candidate to be turned into structures</Name>
// <Id>ND1304:ClassesThatAreCandidateToBeTurnedIntoStructures</Id>

warnif count > 0 
from t in JustMyCode.Types where 
  t.IsClass &&
  t.IsSealed &&
 !t.IsGeneratedByCompiler &&
 !t.IsStatic &&
 !t.IsGeneric &&

  t.NbChildren == 0 &&    // Must not have children

  t.Constructors.All(c => c.NbParameters > 0) && // Must not have parameterless ctor, struct cannot have custom parameterless ctor

  t.IsImmutable && // Structures should be immutable type.

  // Must have fields and all fields must be of value-type
  t.InstanceFields.Count() > 0 &&
  t.InstanceFields.All(f => f.FieldType != null) &&
  t.InstanceFields.All(f => f.FieldType.IsStructure || f.FieldType.IsEnumeration) &&

  // Must not implement interfaces to avoid boxing mismatch 
  // when structures implements interfaces.
  t.InterfacesImplemented.Count() == 0 &&

  // Must derive directly from System.Object
  t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1 &&

  // Must not be a serializable class because a structure should be immutable
  // and serialized types are mutable.
  !t.TypesUsed.Any(t1 => t1.IsAttributeClass && 
    (t1.ParentNamespace.Name == "Newtonsoft.Json" || t1.ParentNamespace.Name == "System.Text.Json")
  ) &&
  !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) &&
  !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) &&
  !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) &&    
  !t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) &&
  !t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) &&
  !t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) &&

  // ASP.NET Core ViewModel and Repository
  !t.SimpleName.EndsWithAny("Model","Repository") &&
  !t.IsUsing("System.ComponentModel.DataAnnotations".AllowNoMatch().MatchNamespace()) &&

  // Must not be an entry point class like 'Program'
  !t.Methods.Any(m => m.IsEntryPoint)

  // && !t.IsPubliclyVisible  <-- You might want to add this condition if 
  //                      you are developing a framework with classes 
  //                      that are intended to be sub-classed by 
  //                      your clients.
  let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t))

select new { 
   t, 
   t.SizeOfInst, 
   t.InstanceFields,
   methodsUser,
   Debt = (5 + 1*methodsUser.Count()).ToMinutes().ToDebt(),
   Severity = Severity.Info
} 

//<Expl>
// The *{0}* could be declared as a structure 
// because it is immutable, it has simple fields, it is sealed
// and it doesn't have a base class.
//</Expl>

//<Desc>
// Structures suit **lightweight values** (like *Int32* or *Boolean*): as
// *values*, they are not managed by the garbage collector, cheaper for
// plenty of *short lived* instances. This rule matches classes looking like
// *lightweight values*:
//
// - It has instance fields, all typed with value-types.
//
// - It is immutable.
//
// - It implements no interfaces.
//
// - It has no parameterless constructor, is not generic.
//
// - It has no derived classes and derives directly from *System.Object*.
//
// - Not an ASP.NETCore ViewModel (name ending with *Model*) or Repository.
//
// Whether instances are numerous *short-lived* objects is not checked: these
// criteria are indications, only you can decide if the change is
// *performance wise*.
//</Desc>

//<HowToFix>
// Use the keyword *struct* instead of *class*.
//
// **CAUTION:** first understand the **deep implications** explained here:
// https://blog.ndepend.com/class-vs-struct-in-c-making-informed-choices/
//
// Estimated Debt: 5 minutes per class plus 1 minute per method using it.
//</HowToFix>

Design: all 16 items