Design Patterns - Singleton

A singleton is a class designed to only ever have one instance.

Singleton Features

  • At any time, only 0 or 1 instance of the Singleton class exists in the application
  • Singleton classes are created without parameters
  • Assume lazy instantiation as the default
  • A single, private, parameterless constructor
  • Sealed class
  • A private, static field holds the only reference to the instance
  • A public static method provides access to the field

Naive implementation of Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace Singleton
{
#nullable enable
public sealed class Singleton
{
private static Singleton? _instance;
public static Singleton Instance
{
get
{
// lazy instantiate
Logger.Log("Instance called");
return _instance ??= new Singleton();
}
}

private Singleton()
{
// cannot be created except within this class
Logger.Log("Constructor invoked");
}
}
}

The problem of this native implementation is Thread safety. In multi-thread environment, the If block can be reached by multiple threads concurrently, resulting in multiple instantiations of Singleton.

Thread Safe Singleton

One way to make sure the Singleton Instance will not be created in a multiple thread environment is to use a lock

1
private static readonly object padlock = new object();

This lock is a private static readonly object that will be shared by all references to the Singleton instance

1
2
3
4
5
6
7
// this lock is used on every reference to Singleton
lock (padlock)
{
Logger.Log("Instance called.");
// lazy instantiation
return _instance ??= new Singleton();
}

A slight better way to add lock is to use the double check locking pattern, this gives us a better performance because we don’t need to check lock very often

1
2
3
4
5
6
7
8
9
10
11
if (_instance == null)
{
// this lock is used on every reference to Singleton
lock (padlock)
{
Logger.Log("Instance called.");
// lazy instantiation
_instance = new Singleton();
}
}
return _instance;
  • Locking adds thread safety
  • First version imposes lock on every access, not just first time
  • Second version is better, but has some issues with the ECMA CLI spec that may be a concern
  • Neither approach works as well as the next ones

Static Constructors

  • C# static constructors only run once per app domain
  • static constructors are called when any static member of a type is referenced
  • Make sure you use an explicit static constructor to avoid issue with C# compiler and beforefieldinit (beforefieldinit is a hint the compiler uses to let it know static initializers can be called sooner, and this is the default if the type does not have an explicit static constructor. Adding an explicit static constructor avoids having beforefieldinit applied, which helps make our singleton behavior lazier)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public sealed class StaticConstructorSingleton : ISingleton
{
// reading this will initialize the instance
public static readonly string GREETING = "Hi!";

public static StaticConstructorSingleton Instance
{
get
{
Logger.Log("Instance called");
return Nested._instance;
}
}

private class Nested
{
//Tell C# compiler not to mark type as beforefieldinit
static Nested()
{

}
internal static readonly StaticConstructorSingleton _instance = new StaticConstructorSingleton();
}

private StaticConstructorSingleton()
{
// cannot be created except within this class
Logger.Log("Constructor invoked.");
}
}

This approach is Thread-safe, no locks (good performance), but is complex and non-intuitive.

Lazy

One difference between this approach and the naive approach is that the private static readonly field is type of Lazy<Singleton> rather than just Singleton, this field is initilized at construction to create a new Lazy<T> instance, and a lambda function is passed into the Lazy<T> constructor with the logic needed to create the singleton instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public sealed class LazyTSingleton
{
// reading this will initilize the instance
private static readonly Lazy<LazyTSingleton> _lazy = new Lazy<LazyTSingleton>(() => new LazyTSingleton());

public static LazyTSingleton Instance
{
get
{
Logger.Log("Instance called.");
return _lazy.Value;
}
}

private LazyTSingleton()
{
// cannot be created except within this class
Logger.Log("Constructor invoked.");
}
}

This approach is very easy to understand and has the performance and thread safe feature.

Singletons vs Static Classes

Singletons Static Classes
Can implement interfaces No interfaces
Can be passed as an argument Cannot be passed as arguments
Can be assigned to variables Cannot be assigned
Support polymorphism Purely procedural
Can have state Can only access global state
Can be serialized No support for serialization

Singleton Behavior Using Containers(IoC)

  • .NET Core has built-in support for IoC Containers
  • Classes request dependencies via constructor
  • Classes should follow Explicit Dependencies Principle
  • Container manages abstraction-implementation mapping
  • Container manages instnace lifetime

Manage Lifetime Using Container, not Class Design

Easily manage and modify individual class lifetimes using an IoC container

Can also be used by any service, console application, etc…

1
2
3
4
5
6
7
public void ConfigureService(ServiceCollection services) 
{
services.AddTransient<IOrderService, OrderService>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddSingleton<IConnectionManager, ConnectionManager>();
services.AddSingleton<SomeInstance>(new SomeInstance);
}

Transient: A new instance of the type is provided any time a class requests that type as a dependency.

Scope: Define a scope and any instance requested within that scope will be shared if it’s requested again within that socpe. The first request will get a new instance and all subsequent requests in that scope will get that same instance.

Singleton: only one instance will be created and shared by all references. Just like the Singleton pattern.

IoC containers are probably the best approach in systems that already use them. Otherwise, Laszy<T> provides an elegant, easily understood approach.

Summary

  • A Singleton class is designed to only ever have one instance created.
  • The Singleton pattern makes the class itself responsible for enforcing Singleton behavior
  • It’s easy to get the pattern wrong when implementing by hand
  • Lazy<T> is one of the better ways to apply the pattern
  • Singletons are different from Static Classes
  • IoC/DI containers are usually a better place to manage instance lifetime in .NET applications.