Avoid the Singleton pattern

Rule ND1210 Object Oriented Design

The singleton pattern ensures a class has only one runtime instance: http://en.wikipedia.org/wiki/Singleton_pattern

Avoid singletons: they make code less testable (the same instance is forced across unit tests) and harder to maintain (the static Instance getter gives uncontrolled access to shared state, degrading the code over time). Only singletons with mutable instance fields are matched, since the pitfalls come from unregulated access to instance data. More details: https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/

How to fix

The problem is the usual singleton syntax (a static field holding the instance), which hinders testability — not having a single runtime instance. Create the instance at program startup and pass it explicitly to all classes and methods needing it. With multiple singletons, consider consolidating them into a single execution context, easier to propagate.

Estimated Debt: 3 minutes per method relying on the singleton — often hundreds of methods, hence hours of refactoring. Severity is Critical because a singleton can prevent the whole program from being testable and maintained.

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>Avoid the Singleton pattern</Name> 
// <Id>ND1210:AvoidTheSingletonPattern</Id>
warnif count > 0
from t in Application.Types
where !t.IsStatic && !t.IsAbstract && (t.IsClass || t.IsStructure)

// All ctors of a singleton are private
where t.Constructors.Where(ctor => !ctor.IsPrivate).Count() == 0

// Require mutable instance fields to be shared across the several clients of the single object.
let mutableInstanceFields = t.InstanceFields.Where(f => !f.IsImmutable)
where mutableInstanceFields.Any()

// A singleton contains one or several static fields of its parent type, 
// or of an interface implemented by its parent type,
// to reference the unique instance
let staticFieldInstances = t.StaticFields.WithFieldTypeIn(t.InterfacesImplemented.Concat(t))
where staticFieldInstances.Count() == 1

let staticFieldInstance = staticFieldInstances.Single()
let methodsUsingField = staticFieldInstance.MethodsUsingMe
let methodsUsingField2 = methodsUsingField.Concat(methodsUsingField.SelectMany(m => m.MethodsCallingMe))

select new { 
   t, 
   staticFieldInstance,
   methodsUsingField2,
   mutableInstanceFields ,
   Debt = (3*methodsUsingField2.Count()).ToMinutes().ToDebt(),
   AnnualInterest = (10+methodsUsingField2.Count()).ToMinutes().ToAnnualInterest()
}

//<Expl>
// The *{0}* shouldn't rely on this implementation of the *Singleton* pattern.
// While it's fine to have a single instance of a class at runtime, 
// using this pattern to enforce a single instance at test-time is problematic, 
// as different tests may require their own instances.
//</Expl>

//<Desc>
// The *singleton pattern* ensures a class has only one runtime instance:
// http://en.wikipedia.org/wiki/Singleton_pattern
//
// Avoid singletons: they make code less testable (the same instance is
// forced across unit tests) and harder to maintain (the static *Instance*
// getter gives uncontrolled access to shared state, degrading the code over
// time). Only singletons with *mutable instance fields* are matched, since
// the pitfalls come from unregulated access to instance data. More details:
//  https://blog.ndepend.com/the-proper-usages-of-the-keyword-static-in-c/
//</Desc>

//<HowToFix>
// The problem is the *usual singleton syntax* (a static field holding the
// instance), which hinders testability — not having a single runtime
// instance. Create the instance at program startup and pass it explicitly to
// all classes and methods needing it. With multiple singletons, consider
// consolidating them into a single execution context, easier to propagate.
//
// Estimated Debt: 3 minutes per method relying on the singleton — often
// hundreds of methods, hence hours of refactoring. Severity is **Critical**
// because a singleton can prevent the whole program from being testable and
// maintained.
//</HowToFix>

Object Oriented Design: all 14 items