How to use Singleton pattern in C#

By FoxLearn 12/27/2024 9:50:33 AM   490
The Singleton is a creational design pattern that ensures a class has only one instance.

It provides a global access point to that single instance, preventing the creation of additional instances of the class.

Structure

singleton pattern in c#

The Singleton pattern addresses two problems, but it violates the Single Responsibility Principle:

  • It ensures a class has only one instance, typically to control access to shared resources like a database or file.
  • It provides a global access point to that instance, similar to a global variable but with protection against accidental overwriting by other code.

While the Singleton prevents the creation of multiple instances by returning the same object when requested, it consolidates this logic in one class, avoiding scattered code.

Despite its widespread use, some may call something a "singleton" even if it only addresses one of these issues.

How to implement Singleton pattern in C#?

class SingletonPattern
{
    static void Main()
    {
        Singleton o1 = Singleton.Instance();
        Singleton o2 = Singleton.Instance();
        if (o1 == o2)
            Console.WriteLine("Objects are the same instance");
        Console.ReadKey();
    }
}

class Singleton
{
    private static Singleton _instance;

    public static Singleton Instance()
    {
        if (_instance == null)
            _instance = new Singleton();
        return _instance;
    }
}

The Singleton pattern is applicable when a class should have only one instance shared across different parts of a program, such as a database object. It restricts object creation to a special method, which either creates a new instance or returns an existing one.

Here's a real-world example of the Singleton pattern applied to a logging system.

In this case, the Logger class ensures that only one instance of the logger is used across the program. The getInstance method controls access to the single instance, and all logging messages go through the same logger object.

// The Logger class defines the `GetInstance` method that provides
// access to the same instance of a logging service throughout the program.
public class Logger
{
    // The field for storing the singleton instance should be declared static.
    private static Logger _instance;

    // The singleton's constructor should always be private to prevent
    // direct construction calls with the `new` operator.
    private Logger()
    {
        // Some initialization code, such as opening a log file
        // or setting up logging configuration.
        // ...
    }

    // The static method that controls access to the singleton instance.
    public static Logger GetInstance()
    {
        // Use lazy initialization to create the instance only when it's needed.
        if (_instance == null)
        {
            lock (typeof(Logger)) // Ensure thread safety.
            {
                if (_instance == null) // Double-check if the instance is still null.
                {
                    _instance = new Logger();
                }
            }
        }

        return _instance;
    }

    // Business logic for logging messages.
    public void Log(string message)
    {
        // For instance, log all messages to a file, console, or remote server.
        // You can include additional logic like timestamps or log levels.
        // Example: Write to console.
        Console.WriteLine($"{DateTime.Now}: {message}");
    }
}

public class Application
{
    public static void Main()
    {
        // Access the logger instance using the Singleton pattern.
        Logger logger1 = Logger.GetInstance();
        logger1.Log("Application started");

        // All other references to Logger will get the same instance.
        Logger logger2 = Logger.GetInstance();
        logger2.Log("Another log message");

        // Both `logger1` and `logger2` will refer to the same instance.
    }
}

Applicability

Use the Singleton pattern when you need stricter control over global variables, as it ensures only one instance exists and prevents accidental replacement by other code. If needed, this limitation can be adjusted by modifying the getInstance method to allow multiple instances.