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 | namespace Singleton |
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 | // this lock is used on every reference to 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 | if (_instance == null) |
- 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 | public sealed class StaticConstructorSingleton : ISingleton |
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 | public sealed class LazyTSingleton |
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 | public void ConfigureService(ServiceCollection services) |
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.