Singleton Design Pattern: An Easy Learning

Singleton Design Pattern: An Easy Learning

14 Nov 2024
Intermediate
205K Views
18 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Singleton Design Pattern

The Singleton Design Pattern is a key notion in C# object-oriented programming. It assures that a class has just one instance and gives a global interface to that instance. This can be especially helpful in situations where only one item is required to coordinate operations throughout the system.

In this C# tutorial, we'll look at the Singleton Design Pattern. What is the singleton design pattern in C#? And when should I use it? We'll also look at the Singleton Design Pattern in C#, with examples. So, let us begin by addressing the question, "What is the Singleton Design Pattern?"

What is the Singleton Design Pattern?

  • The Singleton Design Pattern guarantees that a class has only one instance and gives a global access point to it.
  • It is widely used to manage shared resources, such as configuration settings and logging.
  • Consider a corporation that requires a single point of communication for all personnel; the CEO serves as this single point.
  • In a software system, a Singleton could be used to manage a configuration file, guaranteeing that all program components use the same settings consistently.

Singleton Pattern - UML Diagram

The UML class diagram for the implementation of the Singleton design pattern is given below:

Singleton Pattern - UML Diagram

When should you use the Singleton Design Pattern in C#?

Here's a brief overview of when to apply the Singleton Design Pattern in real-time applications:

  • Configuration management: Process of ensuring that all configuration settings are consistent.
  • Logging Services: To unify and organize logging throughout the application.
  • Database Connections: You can manage and reuse a single database connection or connection pool.
  • Cache management: Involves keeping a single cache instance to ensure consistent data retrieval.
  • Thread Pool Management: Deals with the efficient management and reuse of threads.
How to Implement Singleton Pattern in C# code

There are various approaches to implementing the Singleton Pattern in C#.

  1. Basic Singleton Implementation
  2. Thread-Safe Singleton Implementation
  3. Lazy Initialization Singleton Implementation
  4. Eager Initialization Singleton Implementation

1. Basic Singleton Implementation

  • In the Basic Singleton Implementation, a private static variable stores the class's sole instance.
  • The private constructor restricts external instantiation.
  • The public static Instance property allows access to a single instance, which is created if it does not already exist.
  • This method ensures that just one instance of the class is produced and utilized throughout the application.

Example


using System;

// LogManager class (Singleton implementation)
public class LogManager
{
    private static LogManager _instance;
    private static readonly object _lock = new object();

    private LogManager() { }

    public static LogManager Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        _instance = new LogManager();
                    }
                }
            }
            return _instance;
        }
    }

    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

// Main class
public class Program
{
    public static void Main(string[] args)
    {
        LogManager logger = LogManager.Instance;
        logger.Log("This is a log message!");
        logger.Log("Singleton pattern in action.");
    }
}
            

# Singleton pattern in Python
class LogManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(LogManager, cls).__new__(cls)
        return cls._instance

    def log(self, message):
        print(f"Log: {message}")

# Main function
if __name__ == "__main__":
    logger = LogManager()
    logger.log("This is a log message!")
    logger.log("Singleton pattern in action.")
            

public class LogManager {
    private static LogManager instance;

    private LogManager() {}

    public static synchronized LogManager getInstance() {
        if (instance == null) {
            instance = new LogManager();
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("Log: " + message);
    }

    public static void main(String[] args) {
        LogManager logger = LogManager.getInstance();
        logger.log("This is a log message!");
        logger.log("Singleton pattern in action.");
    }
}
            

class LogManager {
    constructor() {
        if (LogManager.instance == null) {
            LogManager.instance = this;
        }
        return LogManager.instance;
    }

    log(message) {
        console.log(`Log: ${message}`);
    }
}

// Main
const logger = new LogManager();
logger.log("This is a log message!");
logger.log("Singleton pattern in action.");
            

class LogManager {
    private static instance: LogManager;

    private constructor() {}

    public static getInstance(): LogManager {
        if (!LogManager.instance) {
            LogManager.instance = new LogManager();
        }
        return LogManager.instance;
    }

    public log(message: string): void {
        console.log(`Log: ${message}`);
    }
}

// Main
const logger = LogManager.getInstance();
logger.log("This is a log message!");
logger.log("Singleton pattern in action.");
            

Explanation

This code creates a LogManager class that follows the Singleton pattern to ensure that only one instance exists. This is accomplished by the use of a private static variable _instance and a private constructor, as well as a public static Instance property that provides access to the single instance. The Log method writes messages to the terminal.

Output

Log: This is a log message!
Log: Singleton pattern in action.

2. Thread-Safe Singleton Implementation

  • The Thread-Safe Singleton Implementation guarantees that just one instance of a class is produced, even in a multithreaded environment.
  • It employs a lock statement to synchronize access to the instance generation process, preventing other threads from creating duplicate instances.
  • This technique ensures that only one instance is initialized while allowing for secure concurrent access.

Example

Copy

using System;
using System.Threading;

public class ConfigurationManager
{
    private static ConfigurationManager _instance;
    private static readonly object _lock = new object();

    private ConfigurationManager()
    {
        Console.WriteLine("ConfigurationManager initialized.");
    }

    public static ConfigurationManager Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        _instance = new ConfigurationManager();
                    }
                }
            }
            return _instance;
        }
    }

    public string GetSetting(string key)
    {
        return $"Value for {key}";
    }
}

public class Program
{
    public static void Main()
    {
        var thread1 = new Thread(() =>
        {
            var config1 = ConfigurationManager.Instance;
            Console.WriteLine(config1.GetSetting("DatabaseConnection"));
        });
        
        var thread2 = new Thread(() =>
        {
            var config2 = ConfigurationManager.Instance;
            Console.WriteLine(config2.GetSetting("ApiKey"));
        });
        
        thread1.Start();
        thread2.Start();
        
        thread1.Join();
        thread2.Join();
    }
}
            
Copy

import threading

class ConfigurationManager:
    _instance = None
    _lock = threading.Lock()

    # Private constructor to prevent instantiation from outside
    def __init__(self):
        if ConfigurationManager._instance is not None:
            raise Exception("This class is a singleton!")
        print("ConfigurationManager initialized.")

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = ConfigurationManager()
        return cls._instance

    def get_setting(self, key):
        return f"Value for {key}"

def thread_function(key):
    config = ConfigurationManager.get_instance()
    print(config.get_setting(key))

if __name__ == "__main__":
    # Create multiple threads to access ConfigurationManager
    thread1 = threading.Thread(target=thread_function, args=("DatabaseConnection",))
    thread2 = threading.Thread(target=thread_function, args=("ApiKey",))

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()

            
Copy

public class ConfigurationManager {
    private static ConfigurationManager instance;
    
    private ConfigurationManager() {
        System.out.println("ConfigurationManager initialized.");
    }

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

    public String getSetting(String key) {
        return "Value for " + key;
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            ConfigurationManager config1 = ConfigurationManager.getInstance();
            System.out.println(config1.getSetting("DatabaseConnection"));
        });

        Thread thread2 = new Thread(() -> {
            ConfigurationManager config2 = ConfigurationManager.getInstance();
            System.out.println(config2.getSetting("ApiKey"));
        });

        thread1.start();
        thread2.start();
    }
}
            
Copy

  // JavaScript equivalent of the Singleton pattern code goes here
            
Copy

  // TypeScript equivalent of the Singleton pattern code goes here
            

Explanation

This example shows a thread-safe Singleton pattern in C#. The ConfigurationManager class uses double-checked locking to ensure that only one instance is created, even when there is concurrent access. The Program class generates two threads that access the Configuration Manager. Instance, indicating that the singleton instance is only shared and initialized once. The output confirms the singleton behavior by demonstrating that startup happens only once and settings are accurately received.

Output

ConfigurationManager initialized.
Value for DatabaseConnection
Value for ApiKey

3. Lazy Initialization Singleton Implementation

  • In C#, the Lazy Initialization Singleton Pattern makes sure that the class instance is created only when necessary.
  • This is accomplished by utilizing a static variable to store the instance and a static method to manage its creation.
  • The Lazy<T> class creates a thread-safe instance only when accessed for the first time. This method reduces redundant startup and boosts speed.

Example

 using System;

public class Logger
{
    // Private static instance of the Logger class, initialized lazily
    private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());

    // Private constructor to prevent direct instantiation
    private Logger() 
    {
        // Initialize resources or setup if needed
    }

    // Public static property to access the single instance
    public static Logger Instance => _instance.Value;

    // Method to log messages
    public void Log(string message)
    {
        Console.WriteLine($"Log entry: {message}");
    }
}

class Program
{
    static void Main()
    {
        // Access the Logger instance
        Logger logger = Logger.Instance;

        // Use the Logger to log a message
        logger.Log("This is a singleton logger message.");
    }
}   

Explanation

The Logger class uses the sluggish<T> class to implement the Singleton pattern through sluggish initialization. It ensures that only one instance of Logger is created and accessed via the Instance property. The Log method allows you to log messages, and this instance is thread-safe and only created when needed. The Program class explains how to obtain and use the Logger instance to log a message.

Output

Log entry: This is a singleton logger message.

4. Eager Initialization Singleton Implementation

  • In C#, eager initialization for the Singleton pattern creates a single instance of the class during class loading.
  • This is accomplished by declaring a static, read-only instance of the class that is initialized instantly.
  • This approach assures thread safety and simplicity, but it may result in increased memory usage if the instance is generated when it is not required.

Example

 using System;

public class ConfigurationManager
{
    // Private static readonly instance of the ConfigurationManager class, eagerly initialized
    private static readonly ConfigurationManager instance = new ConfigurationManager();

    // Property to store configuration settings
    public string Configuration { get; private set; }

    // Private constructor ensures that the class cannot be instantiated from outside
    private ConfigurationManager()
    {
        // Load configuration settings (e.g., from a file or database)
        Configuration = LoadConfiguration();
    }

    // Public static property to access the single instance
    public static ConfigurationManager Instance => instance;

    // Private method to simulate loading configuration settings
    private string LoadConfiguration()
    {
        // Simulate loading configuration data
        return "Configuration data loaded.";
    }
}

class Program
{
    static void Main()
    {
        // Access the ConfigurationManager instance
        ConfigurationManager configManager = ConfigurationManager.Instance;

        // Display the loaded configuration
        Console.WriteLine(configManager.Configuration);
    }
}

Explanation

This ConfigurationManager class uses eager initialization to generate a single, globally available instance at class load time. The instance is created using a private constructor that loads configuration settings immediately. The Instance property gives you access to this pre-created instance, which ensures that configuration data is available throughout the application's lifecycle.

Output

Configuration data loaded.

Advantages of Singleton Design Pattern

The Singleton Design Pattern offers the following advantages:

  • Controlled Access to the Sole Instance: Ensures that only one instance of the class is generated, with a single point of access to it.
  • Reduced Memory Footprint: Saves memory by not creating numerous instances of a class.
  • Consistent Access to Shared Resources: Provides a centralized interface for accessing shared resources such as configuration settings or logging, guaranteeing consistency throughout the program.
  • Lazy Initialization: The instance is created only when necessary, potentially improving startup time and resource utilization.
  • Global State Management: Used to manage an application's global state, ensuring that all system components have synchronized access to shared data.

Disadvantages of Singleton Design Pattern

The Singleton Design Pattern has a few drawbacks:

  • Global State Issues: Because a Singleton provides an international point of access, it might cause hidden dependencies between classes, making the system difficult to comprehend and manage.
  • Testing Difficulties: Because singletons have a global state, it isn't easy to separate and test individual components separately.
  • Concurrency Issues: In multithreaded systems, maintaining thread-safe access to the Singleton instance can complicate implementation and potentially cause performance problems.
  • Tight Coupling: Classes that rely on the Singleton may become strongly tied to it, reducing flexibility and making it more difficult to replace or restructure.
Summary
The Singleton Design Pattern guarantees that a class has a single, globally accessible instance. In C#, it can be implemented using basic methods, thread-safe ways with double-checked locking, lazy initialization with Lazy<T>, or eager initialization by constructing the instance during class load time. Each technique balances performance, memory utilization, and initialization timing.

FAQs

The Singleton design pattern's main aim is to ensure that a class has only one instance and gives a global point of access to it.

The Singleton design pattern has disadvantages, including difficult unit testing due to tight coupling and potential concerns with scalability and concurrency in multi-threaded systems.

A real-world example of employing the Singleton design pattern is to manage a global configuration settings object that is accessed throughout an application to ensure consistency and avoid duplicates.

The singleton design restricts class instantiation and ensures that only one instance exists in the Java Virtual Machine. The singleton class must provide a global access point for retrieving the class object. The singleton design is used for logging, driver objects, caching, and thread pooling.
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan, Founder and CEO of ScholarHat by DotNetTricks, is a renowned expert in System Design, Software Architecture, Azure Cloud, .NET, Angular, React, Node.js, Microservices, DevOps, and Cross-Platform Mobile App Development. His skill set extends into emerging fields like Data Science, Python, Azure AI/ML, and Generative AI, making him a well-rounded expert who bridges traditional development frameworks with cutting-edge advancements. Recognized as a Microsoft Most Valuable Professional (MVP) for an impressive 9 consecutive years (2016–2024), he has consistently demonstrated excellence in delivering impactful solutions and inspiring learners.

Shailendra’s unique, hands-on training programs and bestselling books have empowered thousands of professionals to excel in their careers and crack tough interviews. A visionary leader, he continues to revolutionize technology education with his innovative approach.
Accept cookies & close this