RO EN

Singleton Pattern – control of unique instances in .NET

Singleton Pattern – control of unique instances in .NET
Doru Bulubasa
31 October 2025

Among the most popular design patterns, the Singleton Pattern holds a special place. It is simple to understand but also easy to misuse. Its main role is to ensure that there is only one instance of a class throughout the entire application and that it provides a global access point to it.


🧩 What is the Singleton Pattern

Imagine an application where you need a global service, such as:

  • a Logger that writes messages to the console or files;

  • a ConfigManager that keeps the application's settings;

  • a single connection to a database (in certain simple scenarios).

If each class created its own logger or configuration manager, we would end up with multiple, inconsistent instances that are hard to control.

Singleton solves exactly this problem by ensuring a unique and reusable instance throughout the entire application.


⚙️ Simple Implementation in C#

The most classic implementation looks like this:

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

    private Logger() { }

    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                return _instance ??= new Logger();
            }
        }
    }

    public void Log(string message)
    {
        Console.WriteLine($"[{DateTime.Now}] {message}");
    }
}

🔹 private Logger() — the constructor is private, so you cannot create objects directly.

🔹 static Logger Instance — exposes the unique instance.

🔹 lock (_lock) — ensures thread-safety, so that the instance is created only once, even in multi-threaded applications.

Usage:

Logger.Instance.Log("The application has started!");
Logger.Instance.Log("This is a unique instance.");

No matter how many times you call Instance, you will get the same object.


⚡ Modern Variants – Lazy Initialization

In .NET, we can simplify the implementation even more by using the Lazy<T> class:

public sealed class Logger
{
    private static readonly Lazy<Logger> _instance =
        new(() => new Logger());

    public static Logger Instance => _instance.Value;

    private Logger() { }

    public void Log(string message)
    {
        Console.WriteLine($"[{DateTime.Now}] {message}");
    }
}

The advantage?

Lazy<T> provides safe, thread-safe initialization and creates the instance only when needed.


💡 When to Use Singleton

Suitable cases:

  • Services that must exist only once (logging, caching, configuration).

  • Global resource managers (e.g., API keys, configuration files).

  • Unified access to a common component (e.g., translation system).

Cases to avoid:

  • When the object is stateful and that state can change over time.

  • In unit testing, because Singletons can be hard to mock.

  • In large applications, they can create hidden dependencies and make refactoring difficult.


🧠 Alternative: Dependency Injection

In the modern .NET ecosystem, many Singleton cases can be solved more elegantly with Dependency Injection (DI):

Thus, the framework (ASP.NET, Blazor, etc.) manages the lifetime of the instance.

The object is still unique but without violating the dependency inversion principles.

Singleton thus becomes a logical concept, not a hard-coded implementation.


🚀 Conclusion

Singleton Pattern is a simple but powerful pattern when used correctly.

It ensures a unique instance and global access, reducing resource consumption and maintaining application consistency.

However, it must be used carefully: a poorly implemented Singleton can lead to rigid, hard-to-test, and hard-to-maintain code.

Used correctly, in combination with Dependency Injection, it remains one of the pillars of modern .NET architecture.