Potentially Dead Types
Lists potentially dead types: removable because never used by the program. It matches types not used anywhere and types used only by such types — hence the TypesUsingMe column and the depth metric (0 = not used, 1 = used only by unused types, etc…).
Public types are not matched by default (client code not analyzed by NDepend might use them); easily changed. Entity Framework ModelSnapshot classes (used only by the EF infrastructure) are not matched.
Usages from test code (the TestCode view) keep a type alive: a type used only from tests is not matched here but by the rule Code used only by tests, disabled by default.
Public types are not matched by default (client code not analyzed by NDepend might use them); easily changed. Entity Framework ModelSnapshot classes (used only by the EF infrastructure) are not matched.
Usages from test code (the TestCode view) keep a type alive: a type used only from tests is not matched here but by the rule Code used only by tests, disabled by default.
How to fix
Static analysis cannot list dead types exactly: types can be used dynamically (like through reflection). Investigate each match; if truly unused, remove it to avoid maintaining useless code, or comment it out with a note on future intentions.
If a used type is still matched, tag it with IsNotDeadCodeAttribute from NDepend.API.dll (or your own attribute, adapting the rule).
Debt is 15 minutes (quick investigation). Annual Interest is proportional to the type's #lines of code: the bigger, the more it slows down maintenance.
If a used type is still matched, tag it with IsNotDeadCodeAttribute from NDepend.API.dll (or your own attribute, adapting the rule).
Debt is 15 minutes (quick investigation). Annual Interest is proportional to the type's #lines of code: the bigger, the more it slows down maintenance.
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>Potentially Dead Types</Name>
// <Id>ND1700:PotentiallyDeadTypes</Id>
warnif count > 0
// Filter procedure for types that shouldn't be considered as dead
let canTypeBeConsideredAsDeadProc = new Func<IType, bool>(
t => !t.IsPublic && // Public types might be used by client applications of your assemblies.
t.Name != "Program" &&
!t.IsGeneratedByCompiler &&
!t.IsUsedFromAssemblyAttributes &&
// If you don't want to link NDepend.API.dll, you can use your own
// IsNotDeadCodeAttribute or UsedImplicitlyAttribute
// and adapt the source code of this rule.
!t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) &&
!t.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch()) &&
!t.Methods.Any(m => m.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch())) &&
!t.Properties.Any(p => p.HasAttribute("JetBrains.Annotations.UsedImplicitlyAttribute".AllowNoMatch())) &&
// Exclude static types that define only const fields
// because they cannot be seen as used in IL code.
!(t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()) &&
// Entity Framework ModelSnapshot classes are used only by the EF infrastructure.
!t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch()) &&
// ASP.NET MVC Controller classes are used only by the infrastructure.
!t.SimpleName.EndsWith("Controller") &&
!t.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch()) &&
!t.DeriveFrom("Microsoft.AspNetCore.Mvc.ControllerBase".AllowNoMatch()) &&
!t.HasAttribute("Microsoft.AspNetCore.Mvc.ControllerAttribute".AllowNoMatch()) &&
// Types holding a module initializer are used by the runtime at assembly load
// (.NET 5+ [ModuleInitializer]): a registration hook and everything it reaches is alive.
!t.Methods.Any(m => m.HasAttribute("System.Runtime.CompilerServices.ModuleInitializerAttribute".AllowNoMatch())) &&
// TypeConverter classes are instantiated by the BCL from the [TypeConverter] attribute,
// often through its string form ("Ns.X, Asm") that IL analysis cannot see.
!t.DeriveFrom("System.ComponentModel.TypeConverter".AllowNoMatch()) &&
// Types used by the Unity engine
!t.HasAttribute("UnityEngine.RuntimeInitializeOnLoadAttribute".AllowNoMatch()) &&
!t.HasAttribute("UnityEngine.InitializeOnLoadAttribute".AllowNoMatch()) &&
!t.DeriveFrom("UnityEditor.AssetPostprocessor".AllowNoMatch()) &&
!(t.IsStatic && t.StaticMethods.Any(m => m.HasAttribute("UnityEditor.MenuItemAttribute".AllowNoMatch()))) &&
!t.Methods.Any(m => m.HasAttribute("UnityEngine.RuntimeInitializeOnLoadMethodAttribute".AllowNoMatch()) ||
m.HasAttribute("UnityEngine.InitializeOnLoadMethodAttribute".AllowNoMatch()))
)
// Select types unused (usages from test code count: a type used only from tests
// is not dead, see the rule 'Code used only by tests')
let typesUnused =
from t in JustMyCode.Types where
t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t)
select t
// Dead types = types used only by unused types (recursive)
let deadTypesMetric = typesUnused.FillIterative(
types => // Unique loop, just to let a chance to build the hashset.
from o in (new object()).ToEnumerable()
let hashset = types.ToHashSetEx()
from t in codeBase.Application.Types.UsedByAny(types).Except(types)
where canTypeBeConsideredAsDeadProc(t) &&
// Select types used only by types already considered as dead
t.TypesUsingMe.All(tUser => hashset.Contains(tUser))
select t)
from t in deadTypesMetric.DefinitionDomain
let depth = deadTypesMetric[t]
// A type of depth > 0 is used, but only by types themselves dead: the explanation names them
let nbUsers = t.TypesUsingMe.Count()
let deadUsers = t.TypesUsingMe.Take(3).Select(tUser => "*" + tUser.Name + "*").Aggregate(", ") +
(nbUsers > 3 ? " and " + (nbUsers - 3).ToString() + " other" + (nbUsers > 4 ? "s" : "") : "")
select new {
t,
depth,
t.TypesUsingMe,
expl = depth == 0 ?
"is not used within the context of analyzed code and may be a candidate for removal" :
"is used only by the type" + (nbUsers > 1 ? "s " : " ") + deadUsers + ", potentially dead too, " +
"and may be a candidate for removal along with " + (nbUsers > 1 ? "them" : "it"),
Debt = 15.ToMinutes().ToDebt(),
AnnualInterest = (10 + (t.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest()
}
//<Expl>
// The *{0}* {3}.
//</Expl>
//<Desc>
// Lists *potentially* **dead types**: removable because never used by the
// program. It matches types not used anywhere and types used only by such
// types — hence the *TypesUsingMe* column and the *depth* metric (*0* = not
// used, *1* = used only by unused types, etc…).
//
// *Public* types are not matched by default (client code not analyzed by
// NDepend might use them); easily changed. Entity Framework ModelSnapshot
// classes (used only by the EF infrastructure) are not matched.
//
// Usages from **test code** (the *TestCode* view) keep a type alive: a type
// used only from tests is not matched here but by the rule *Code used only
// by tests*, disabled by default.
//</Desc>
//<HowToFix>
// *Static analysis* cannot list dead types *exactly*: types can be used
// *dynamically* (like through reflection). Investigate each match; if truly
// unused, remove it to avoid maintaining useless code, or comment it out
// with a note on future intentions.
//
// If a used type is still matched, tag it with **IsNotDeadCodeAttribute**
// from *NDepend.API.dll* (or your own attribute, adapting the rule).
//
// **Debt** is 15 minutes (quick investigation). **Annual Interest** is
// proportional to the type's #lines of code: the bigger, the more it slows
// down maintenance.
//</HowToFix>
Open in the interactive Rules Explorer · All default rules
Dead Code: all 5 items
- Potentially Dead Types ND1700
- Potentially Dead Methods ND1701
- Potentially Dead Fields ND1702
- Wrong usage of IsNotDeadCodeAttribute ND1703
- Code used only by tests ND1704