Classes that are candidate to be turned into structures
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.
- 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.
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>
Open in the interactive Rules Explorer · All default rules
Design: all 16 items
- Avoid custom delegates ND1300
- Types with disposable instance fields must be disposable ND1301
- Disposable types with unmanaged resources should declare finalizer ND1302
- Methods that create disposable object(s) and that don't call Dispose() ND1303
- Disposable Types Must Unsubscribe Events ND1315
- Classes that are candidate to be turned into structures ND1304
- Avoid namespaces with few types ND1305
- Nested types should not be visible ND1306
- Declare types in namespaces ND1307
- Empty static constructor can be discarded ND1308
- Instances size shouldn't be too big ND1309
- Attribute classes should be sealed ND1310
- Don't use obsolete types, methods or fields ND1311
- Do implement methods that throw NotImplementedException ND1312
- Override equals and operator equals on value types ND1313
- Boxing/unboxing should be avoided ND1314