How to Implement Thread-Safe Singleton in C
The Singleton design pattern ensures that a class has only one
instance throughout the application lifecycle and provides a global
access point to that instance. In a multi-threaded C# application, naive
implementations can cause race conditions, resulting in multiple
instances being created simultaneously. This article explains the most
reliable and thread-safe ways to implement a Singleton in C#, focusing
on modern idiomatic approaches such as Lazy<T>,
static initialization, and classic double-checked locking.
Common Requirements for a C# Singleton
Regardless of the thread-safety mechanism chosen, every C# Singleton must satisfy three basic criteria:
- A private, parameterless constructor: Prevents
other classes from instantiating it with the
newoperator. - A sealed class: Prevents subclassing, which could lead to multiple instances.
- A public static property: Provides global access to the single instance.
1. Using Lazy<T>
(Recommended)
The cleanest, most idiomatic, and inherently thread-safe approach in
modern C# (.NET 4.0 and later) is using the
System.Lazy<T> type. It handles thread safety, lazy
evaluation, and exception caching automatically behind the scenes.
public sealed class Singleton
{
private static readonly Lazy<Singleton> _instance =
new Lazy<Singleton>(() => new Singleton());
private Singleton()
{
}
public static Singleton Instance => _instance.Value;
}By default, Lazy<T> uses
LazyThreadSafetyMode.ExecutionAndPublication, ensuring only
one thread can execute the factory method and initialize the
instance.
2. Static Initialization (Eager Loading)
The Common Language Runtime (CLR) guarantees that static constructors and field initializers are executed in a thread-safe manner before the class is accessed.
public sealed class Singleton
{
private static readonly Singleton _instance = new Singleton();
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}
private Singleton()
{
}
public static Singleton Instance => _instance;
}This pattern is simple and thread-safe without explicit lock statements. However, the instance is instantiated as soon as the class is referenced, making it less suitable if initialization is resource-intensive and needs to be deferred until explicitly required.
3. Double-Checked Locking
Double-checked locking avoids acquiring a lock every time the instance is accessed, locking only during the initial creation.
public sealed class Singleton
{
private static Singleton? _instance;
private static readonly object _lock = new object();
private Singleton()
{
}
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
}While effective, this approach is more verbose and prone to
implementation errors compared to Lazy<T>.
Conclusion
For almost all modern C# applications,
Lazy<T> is the preferred solution
because it combines true lazy evaluation, guaranteed thread safety, and
minimal boilerplate code. Use static initialization if eager loading is
acceptable, and avoid manual locking mechanisms unless maintaining
legacy codebases.