Singleton Pattern in C#

Ankur CharanAnkur Charan
6 min read

The singleton pattern is a creational design pattern that restricts the instantiation of a class to a single object.
All singleton implementation have 4 common characteristics

  • A single default (parameter less) private constructor.
    It prevents other classes from instantiating it. It also prevent subclassing. If it gets subclasses, then those classes can create an instance and pattern is violated. Factory pattern can be used if you need single instance of the base type, but the exact type is known until runtime.

  • The class is sealed. Unnecessary, due to the above point, but may help the JIT to optimise.

  • A static variable which holds a reference to the single created instance.

  • A public static means of getting the reference to the single created instance

Naive Singleton

public sealed class NaiveSingleton
{
    private static NaiveSingleton? _instance;

    // private constructor to prevent instantiation from outside
    private NaiveSingleton() { }

    // public method to get the instance of the singleton
    public static NaiveSingleton GetInstance()
    {
        if (_instance == null)
        {
            _instance = new NaiveSingleton();
        }
        return _instance;
    }
}

This particular method isn’t thread safe. Two different threads can evaluate _instance == null as true and then both of those threads will create a new instance of the Singleton.

Thread Safe Using Double-Check Locking

public sealed class Singleton
{
    private static Singleton? _instance;
    private static readonly object _lock = new object();

    // private constructor to prevent instantiation from outside
    private Singleton() { }

    // Public method to get the instance of the singleton
    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }
}

Imagine that the program has just been launched.
Since, there's no Singleton instance yet, multiple threads can simultaneously pass the first if (_instance == null) conditional and reach to the point where it request a lock on the object lock(_lock) almost at the same time. The first of them will acquire lock and will proceed further, while the rest will wait here.
The first thread to acquire the lock, reaches the second if (_instance == null)conditional, goes inside and creates the Singleton instance. Once it leaves the lock block, a thread that might have been waiting for the lock release may then enter this section. But since the Singleton field is already initialized, the thread won't create a new object.

Sometimes, you might find the first conditional missing and directly locking the object. But this attempts to reduce the overhead of locking by first checking if the instance exists outside of the lock, and only locking if it's not instantiated already.

Volatile with Java

It doesn't work in Java. Odd thing to mention here, but if there’s a chances you’ll be writing playing around Java too.
Java allowed instruction reordering by Java compiler. So another thread could see _instance != null before the constructor is finished running, leading to strange bugs like using an incompletely constructed object. (chatgpt if you’re curious here).
To prevent this redording you must use volatile keyword in Java to prevent this reordering.

for your reference, java code with double check locking and using volatile keyword.

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Somewhat Lazy, Thread-safe without locks

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 constructors in C# are specified to execute only 
    //   when an instance of the class is created or a static member is referenced, 
    //   and to execute only once per AppDomain 
    // - not as lazy. if you have static members other than Instance, 
    //   the first reference to those members will involve creating the instance.
    static Singleton () { }

    // private constructor to prevent instantiation from outside
    private Singleton () { }
}

But why is it thread-safe and how lazy is it?
In C#, a static constructor runs automatically the first time you either create an object of the class or access one of its static members—and it only runs once for the entire lifetime of the application (within each AppDomain).
It is not as lazy, i.e. if you have static members other than _instance, the first reference to those members will involve creating the instance.

Full Lazy Instantiation

public sealed class Singleton
{
    // private so no one can create an instance of this class
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            // instantiation is triggered by the first 
            // reference to the static member of the nested class,
            // which only occurs in Instance.
            return Nested.Instance;
        }
    }

    private class Nested
    {
        // static constructor is called only once, when the type is first accessed.
        // it ensures that the instance is created in a thread-safe manner.
        static Nested() { }

        internal static readonly Singleton Instance = new Singleton();
    }
}

Read comments inline. Instantiation is triggered by the first reference to the static member of the nested class. So this only run the static constructor of Nested class when the Nested.Instance is referenced and it is only referenced in the enclosing class.

Using Lazy<T>

It provides lazy initialization with access from multiple threads.

public sealed class Singleton
{
    private static readonly Lazy<Singleton> _lazyInstance =
        new Lazy<Singleton>(() => new Singleton());

    // private constructor to prevent instantiation from outside
    private Singleton() { }

    // Public method to get the instance of the singleton
    public static Singleton Instance
    {
        get
        {
            return _lazyInstance.Value;
        }
    }
}

Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program.
Lazy<T> initialization occurs the first time the Lazy.Value property is accessed.

You can use LazyThreadSafetyMode to specify how Lazy<T> instance synchronizes access among multiple threads.
Lazy<Singleton> _lazy = new Lazy<Singleton>(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);

  • LazyThreadSafetyMode.None - The Lazy<T> instance is not thread safe; if the instance is accessed from multiple threads, its behavior is undefined.

  • LazyThreadSafetyMode.PublicationOnly - When multiple threads try to initialize a Lazy<T> instance simultaneously, all threads are allowed to run the initialization method (or the parameterless constructor, if there is no initialization method). The first thread to complete initialization sets the value of the Lazy instance.This is referred to as Publication in the field names. That value is returned to any other threads that were simultaneously running the initialization method, unless the initialization method throws exceptions on those threads. Any instances of T that were created by the competing threads are discarded. Effectively, the publication of the initialized value is thread-safe in the sense that only one of the initialized values can be published and used by all threads.

  • LazyThreadSafetyMode.ExecutionAndPublication - Locks are used to ensure that only a single thread can initialize a Lazy instance in a thread-safe manner. Effectively, the initialization method is executed in a thread-safe manner (referred to as Execution in the field name). Publication of the initialized value is also thread-safe in the sense that only one value may be published and used by all threads.


References:
- https://refactoring.guru/design-patterns/singleton
- https://csharpindepth.com/Articles/Singleton


Thanks for reading.

LinkedIn: https://www.linkedin.com/in/ankurcharan
Twitter: https://twitter.com/ankurcharan

0
Subscribe to my newsletter

Read articles from Ankur Charan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ankur Charan
Ankur Charan

Software Engineer with 3+ years of experience just casually sharing what I can.