top of page

Understanding the Singleton Design Pattern: A Comprehensive Guide

Updated: Nov 13, 2023

Title: Demystifying Singleton Design Pattern in C#: Double Locking, Basic, Lazy, and Eager Loading


Introduction


The Singleton design pattern is a widely used creational pattern that ensures a class has only one instance and provides a global point of access to it. This pattern is particularly useful when you want to control access to a resource or when a single instance of a class is required to coordinate actions within a system. In this blog post, we'll explore various implementations of the Singleton pattern in C#, including Basic, Double Locking, Lazy Loading, and Eager Loading, with code examples to illustrate each approach.



Singleton Design Pattern


Basic Implementation


The basic implementation of the Singleton pattern is the simplest form, ensuring that a class has only one instance and providing a global point of access. Here's a simple example in C#:


public sealed class BasicSingleton
{
    private static BasicSingleton instance;

    private BasicSingleton() { }

    public static BasicSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new BasicSingleton();
            }
            return instance;
        }
    }
}

In this implementation, the "Instance" property ensures that only one instance of "BasicSingleton| is created.


Why Singleton class is sealed?

  1. Maintaining Control: Sealing the Singleton class helps maintain control over the instantiation of the class. If the class is inherited, it might be possible for the derived class to introduce multiple instances, which defeats the purpose of the Singleton pattern.

  2. Preserving the Singleton Contract: The Singleton pattern comes with a contract that there should be only one instance of the class. By sealing the class, you ensure that this contract is not violated through inheritance.

  3. Preventing Unintended Modifications: By sealing the class, you prevent unintended modifications to the Singleton pattern implementation. Inheritance might lead to changes in the behaviour of the Singleton, which can introduce unexpected issues.


The basic implementation of a singleton class, as presented bov, has a potential issue when used in a multithreaded environment. This issue is known as a "race condition," where two or more threads can simultaneously enter the conditional check for creating the singleton instance, leading to the creation of multiple instances.


To address this issue, you can use a locking mechanism to ensure that only one thread at a time can create the singleton instance. The double-checked locking pattern is a common approach to achieving this. Here's the modified basic singleton class implementation with double-checked locking:


Double Checked Locking


Double-checked locking is an improvement over the basic Singleton pattern, adding a second check to avoid unnecessary locking. This can enhance performance in multithreaded scenarios.


public sealed class DoubleLockingSingleton
{
    private static DoubleLockingSingleton instance;
    private static readonly object lockObject = new object();

    private DoubleLockingSingleton() { }

    public static DoubleLockingSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new DoubleLockingSingleton();
                    }
                }
            }
            return instance;
        }
    }
}


The "lockObject" is used to synchronize access to the critical section of code, preventing multiple threads from creating separate instances.


The double-checked locking pattern, as implemented in the BasicSingleton class with a lock, can still have issues related to memory visibility and potential race conditions in certain scenarios, particularly on older versions of the .NET runtime. This is due to the way the .NET runtime handles the ordering of memory operations.


Starting with C# 6.0 and .NET Framework 4.0, the Lazy<T> class provides a more modern and thread-safe way to implement lazy initialization, making it a recommended choice over the double-checked locking pattern for singleton implementations in C#.


Lazy Loading


Lazy loading defers the creation of the instance until it is actually needed. This can be more efficient, especially if the creation of the object is resource-intensive.


public class LazyLoadingSingleton
{
    private static readonly Lazy<LazyLoadingSingleton> lazyInstance = new Lazy<LazyLoadingSingleton>(() => new LazyLoadingSingleton());

    private LazyLoadingSingleton() { }

    public static LazyLoadingSingleton Instance => lazyInstance.Value;
}

Here, the "Lazy<T>" class ensures that the "LazyLoadingSingleton" instance is created only when the `Instance` property is accessed for the first time.


Eager Loading


In contrast to lazy loading, eager loading creates the instance at the time of class initialization, regardless of whether it is immediately needed.


public class EagerLoadingSingleton
{
    private static readonly EagerLoadingSingleton instance = new EagerLoadingSingleton();

    private EagerLoadingSingleton() { }

    public static EagerLoadingSingleton Instance => instance;
}

Eager loading is straightforward, and the instance is readily available whenever the "Instance" property is accessed.


Conclusion


Understanding the various implementations of the Singleton pattern in C# is crucial for designing scalable and efficient applications. Whether you choose basic, double locking, lazy loading, or eager loading depends on your specific use case and performance requirements. Each approach has its advantages and trade-offs, so choose the one that best fits your application's needs.



7,269 views1 comment

1 Comment

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Guest
Nov 13, 2023
Rated 5 out of 5 stars.

😊

Like
bottom of page