Singleton Pattern: Ensuring a Single Instance in .NET Core

Introduction

In the previous article, "Abstract Factory Pattern: Designing Families of Related Objects", we discussed how the Abstract Factory Pattern could be used to create families of related objects without specifying their concrete classes. The Abstract Factory helps achieve flexibility and scalability by allowing you to manage complex relationships between objects.

Now, moving forward, we will explore a more specialized design pattern: the Singleton Pattern. This pattern ensures that a class has only one instance and provides a global point of access to it. It’s one of the most widely used patterns, especially when managing shared resources or services in .NET Core applications.

Understanding the Singleton Pattern

The Singleton Pattern ensures that a class has only one instance, providing controlled access to that single instance. This pattern is especially useful for managing shared resources like configuration settings, logging mechanisms, or database connections. In multithreaded environments, it’s crucial to implement Singleton properly to prevent race conditions.

Key concepts of the Singleton Pattern:

  • Single Instance: The class ensures that only one instance is created and reused.
  • Global Access Point: The single instance is accessible globally.
  • Lazy Initialization: The instance is created only when needed.

In .NET Core, the Singleton Pattern is frequently used in Dependency Injection to ensure that a service has only one instance throughout the application's lifetime.

Key Components

  • Private Constructor: Prevents instantiation from outside the class.
  • Static Instance Field: Stores the single instance of the class.
  • Public Static Method/Property: Provides global access to the instance.
  • Thread Safety (Optional): Ensures thread-safe operations, especially in multithreaded applications.

Example in C#

Let's illustrate the Singleton Method Pattern is used to manage a logging service. We will ensure that only one instance of the logger is used throughout the application.

1. Create the Logger class with a private constructor

Logger.cs

namespace Singleton_Pattern_Demo
{
    public class Logger
    {
        private static Logger _instance;

        private Logger() { }

        //Public static method that provides access to the single instance
        public static Logger GetInstance()
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }
            return _instance;
        }

        //Log message method to demonstrate functionality
        public void LogMessage(string message)
        {
            Console.WriteLine($"Log Entry: {message}");
        }
    }

}

Explanation:

  • Private Constructor: The constructor is private to prevent any other class from creating a new instance of Logger.
  • Static Instance: The _instance field holds the single instance of the Logger class.
  • GetInstance Method: This method ensures that only one instance is created. If _instance is null, it creates a new instance; otherwise, it returns the existing instance.

2. Using the Singleton Logger in the Program

Program.cs

using Singleton_Pattern_Demo;

class Program
{
    static void Main(string[] args)
    {
        // Fetch the single instance of Logger
        Logger logger1 = Logger.GetInstance();
        logger1.LogMessage("First log entry");

        // Fetch the same instance of Logger
        Logger logger2 = Logger.GetInstance();
        logger2.LogMessage("Second log entry");

        // Verifying both logger1 and logger2 refer to the same instance
        if (logger1 == logger2)
        {
            Console.WriteLine("Both loggers refer to the same instance.");
        }
    }
}

Explanation:

  • The program retrieves the same instance of the Logger using the GetInstance() method.
  • Both logger1 and logger2 refer to the same object, proving the Singleton implementation.
  • Log messages are managed by a single instance, which is shared across the application.

Output

Singleton Pattern

Real-World Use Cases

  • Configuration Management: Singleton is commonly used to manage global configurations in applications.
  • Logging Services: In enterprise-level systems, it’s important that only one logger is used across the application.
  • Database Connections: Managing database connections efficiently is critical, and a Singleton can ensure that a single connection pool is maintained.

Benefits of Singleton Pattern

  • Controlled Access: The pattern ensures that only one instance of a class is created, providing controlled access to resources.
  • Global Access: The single instance can be accessed globally, simplifying interaction with services like logging or configuration.
  • Memory Efficiency: Prevents the creation of multiple instances, saving memory in resource-constrained environments.
  • Thread Safety: In multithreaded environments, Singleton instances ensure thread-safe operations if implemented correctly.

Summary

The Singleton Pattern is a straightforward yet powerful design pattern that restricts the instantiation of a class to a single instance. It is widely used in .NET Core applications for managing shared resources like logging, configuration settings, and database connections. By ensuring that only one instance of a class exists, the pattern promotes controlled access and efficient resource usage.

Next Steps

In the next article, "Builder Pattern: Constructing Complex Objects Step by Step", we will explore how the Builder Pattern can be used to construct complex objects with varying configurations. We will dive into how the Builder Pattern allows you to construct objects incrementally and provide practical examples in .NET Core development.

If you find this article valuable, please consider liking it and sharing your thoughts in the comments.

Thank you, and happy coding.