C# Thread-Safe Singleton Pattern Using Lazy

This article provides a concise guide to implementing the Singleton design pattern in C# using the System.Lazy<T> type. You will learn how Lazy<T> simplifies lazy initialization, provides inherent thread safety without explicit lock statements, and reduces boilerplate code while adhering to best software design practices.

The Implementation

The most robust and idiomatic way to implement a thread-safe singleton in modern C# (.NET 4.0 and later) is by leveraging System.Lazy<T>. Here is the complete implementation:

public sealed class Singleton
{
    // Lazy<T> handles thread-safe, deferred instantiation automatically
    private static readonly Lazy<Singleton> _instance = 
        new Lazy<Singleton>(() => new Singleton());

    // Public accessor to get the singleton instance
    public static Singleton Instance => _instance.Value;

    // Private constructor prevents direct instantiation from outside
    private Singleton()
    {
        // Initialization code here
    }

    public void DoWork()
    {
        // Instance method logic
    }
}

Key Components Explained

  1. sealed Class Modifier: Marking the class as sealed prevents other classes from inheriting from it. Inheritance can introduce multiple instances or violate the singleton constraint through derived constructors.

  2. Private Constructor: The parameterless constructor is marked private to ensure that the class cannot be instantiated directly with the new keyword from external code.

  3. static readonly Lazy<Singleton>: The Lazy<T> wrapper encapsulates the instance creation logic. By default, Lazy<T> is thread-safe (LazyThreadSafetyMode.ExecutionAndPublication). This ensures that only one thread can execute the factory delegate, even if multiple threads attempt to access the instance simultaneously.

  4. Instance Property: The public static Instance property exposes the singleton to consumers. Accessing the .Value property triggers the factory method if the instance has not yet been initialized; otherwise, it immediately returns the cached instance.

Advantages of Using Lazy<T>

  • Implicit Thread Safety: You avoid manual double-check locking patterns (lock, volatile), which are error-prone and harder to read.
  • True Lazy Loading: The instance is created strictly on the first call to Singleton.Instance, avoiding premature initialization during static class loading.
  • Performance: Lazy<T> uses highly optimized internal synchronization primitives, minimizing overhead once the object has been initialized.