Singleton Design Pattern in C#

startup, business, people, students, office, strategy, work, technology, company, corporate, communication, young, plan, marketing, computer, design, professional, planning, internet, project, laptop, presentation, web, display, monitor, business, business, business, office, office, office, office, office, work, work, work, technology, technology, marketing, marketing, marketing, computer, computer, computer, computer, laptop

Singleton is a creational design pattern that restricts a class to a single instance and provides a global access point to it. In modern .NET systems, Singleton behavior is typically implemented using dependency injection with a Singleton lifetime rather than manual instantiation logic.

This design patterns in widely used when there is a need of single instance that share common information throughout the application. This simply consists a sealed class, thread safe design and locking technique that ensures efficient design is structured.

Singleton is a creational design pattern that ensures:

  • Only one instance of a class exists in the application.
  • A global access point is provided to access that instance.

In simple terms:

A Singleton controls its own instantiation and prevents anyone else from creating additional objects.

Why Do We Need Singleton?

Some objects represent shared resources and should not have multiple instances.

Real-world examples:

  • Configuration manager
  • Logger
  • Cache manager
  • In-memory state manager
  • Feature flag service
  • Database connection pool manager

If multiple instances are created:

  • Memory waste
  • Conflicting state
  • Inconsistent behavior
  • Resource locking issues
  • Hard-to-debug concurrency bugs

Which is the core problem solved by Singleton Design Pattern?

Let’s assume we have a logger which logs messages throughout the application. In this case if we don’t use singleton-

var logger1 = new Logger();
var logger2 = new Logger();

This will cause unnecessary creation of the objects of Logger. We need same object everywhere since the object has common job to do and need to mantain the instance to be reused.

Now:

  • Two separate objects
  • Two separate states
  • No guarantee of consistency

Singleton ensures:

var logger1 = Logger.Instance;
var logger2 = Logger.Instance;

// logger1 == logger2 → true

Now the single instance is available to use reused.

Building Singleton from Scratch (Step-by-Step)

Basic Version (Not Thread-Safe)

public class Logger
{
    private static Logger _instance;
    
    // Object creation is stopped 
    private Logger()
    {
        Console.WriteLine("Logger Created");
    }
    
    // By Class name static instance will be initialised
    public static Logger GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Logger();
        }

        return _instance;
    }
}

Since the constructor is set to private so further instantiation is not possible. This makes the class intact and prevents from object creation.

GetInstance will make sure the object is created only once so everytime Logger.GetInstance() will be called, this will either return same instance (if already created) otherwise a new one (if not created).

  • Constructor is private → no external instantiation.
  • Static field stores single instance.
  • Instance created only once.

But there is a problem in this-

Suppose in real world scenario there are multiple threads running this program, now what if at the same time 2 threads execute this code and each one gets instance null? They both will create instance and this way, the singleton design rule will be broken.

Solution?

Thread-Safe Singleton (Using Lock)

We can use lock keyword and ensure that only thread can execute the piece of code and for other thread the block should be locked. By this  synchronization mechanism, singleton can be thread safe.

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

    private Logger()
    {
        Console.WriteLine("Logger Created");
    }

    public static Logger GetInstance()
    {
        lock (_lock) // locks for other thread
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }
        }

        return _instance;
    }
}

Now:

  • Only one thread enters critical section.
  • Safe but slightly inefficient.
  • Lock executes every time.

Now the problem of multiple instance creation is solved but still the lock will get executed everytime. This is also an extra effort done by CLR. To reduce that extra overhead. We can use double checking of instance and lock and define it more efficiently.

Singleton Design Pattern – Optimized Version: Double-Check Locking

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

    private Logger()
    {
    }

    public static Logger GetInstance()
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Logger();
                }
            }
        }

        return _instance;
    }
}

Now first this will check is instance already there? If Yes then whole code block will be skipped and that instance will be returned.

If instance is not there, it will check for lock and if lock is not there, the instance will be created with a lock.

So other thread will check the instance first and then lock.

Best Modern Implementation in C#

Option 1: Static Initialization (Eager Loading)

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

    private Logger()
    {
    }

    public static Logger Instance => _instance;
}

It has created a static readonly instance at first go. The constructor is private that’s why object creation is blocked. Sealed class can not be inherited, so it is safe. Why sealed? because inheriting a singleton can cause other object creation by child class and that will break the rule of singleton.

When the class is loaded first in memory

private static readonly Logger _instance = new Logger();

This static initialization will make it thread safe, so we don’t have a need to use lock.

Writing Logger.Instance will bring the singleton same object everytime.

Eager loading makes the instance ready at first load in memory even code does not have a need at that time.

CLR guarantees thread-safe static initialization.

  • No manual locking required.
  • Clean and simple.

To load it lazy (means at first use), we have lazy loading

Option 2: Lazy (Recommended)

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

    private Logger()
    {
    }

    public static Logger Instance => _instance.Value;
}

Since the eager loading was there and was doing job perfectly, we had a problem that it will create the readonly static singleton instance at first load even we dont have a use right now.

Lazy load does this at the time of first use.

Lazy<T> is .Net Class that says, create the object only when it is first requested.

new Lazy(() => new Logger());

When the value is requested for the first time, execute this function to create the object.

Execution Flow

Application starts
↓
Logger class loads
↓
No object created yet
↓
First call to Logger.Instance
↓
Object is created
↓
Same object returned forever

Why this is best:

  • Lazy initialization
  • Thread-safe
  • Clean syntax
  • Production-ready
  • Recommended in modern .NET

Practical Example: Configuration Manager

Imagine you load app configuration from JSON or database.

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

    private Dictionary<string, string> _settings;

    private AppConfigManager()
    {
        Console.WriteLine("Loading configuration...");
        _settings = LoadSettings();
    }

    public static AppConfigManager Instance => _instance.Value;

    private Dictionary<string, string> LoadSettings()
    {
        return new Dictionary<string, string>
        {
            { "AppName", "My SaaS Platform" },
            { "Version", "1.0" }
        };
    }

    public string GetSetting(string key)
    {
        return _settings.ContainsKey(key) ? _settings[key] : null;
    }
}

Usage:

var config = AppConfigManager.Instance;
Console.WriteLine(config.GetSetting("AppName"));

When Should You Use Singleton?

Use when:

  • Shared resource required
  • Expensive initialization
  • One logical instance needed
  • Stateless or controlled shared state

Examples:

  • Cache service
  • Logging service
  • Configuration manager
  • Feature flag provider

When NOT to Use Singleton

Avoid when:

  • Hidden global state
  • Difficult unit testing
  • Request-specific logic
  • Multi-tenant scoped behavior required
  • Overuse causing tight coupling

Singleton is sometimes considered an anti-pattern because It introduces global state.

That’s it for the blog, since we study about Singleton design pattern, keep in mind that DI is a good option when we look for efficient architecture, that we will study in upcoming blogs.

Thanks for reading.

Read more here

Recommended Topics

Popular Tags

.net .NET 6 .Net Core .NET Developers .NET Development Future .NET Productivity .NET programming agentic ai AI Agents ai in dot net AI Tools .NET app.Map ASP.NET Core Azure AI Boilerplate C# C# AI C# Programming circuit breaker pattern Code Assistants Coding Coding best practices Coding in AI Creational Design Patterns Design Patterns Design Patterns in C# dot net dotnet core resilience Factory Design Patterns in C# future of dot net Generative AI Intelligent coding tools Knowledge Lightweight API LLMs .NET Machine Learning .NET MapGet Microservices Minimal API ML.NET Motivational polly v8 resilience architecture REST API Semantic Kernel Singleton Design Pattern in C# Software Architecture The Avinash Joshi TheAvinashJoshi Top 5 AI tools trending coding methods vibe coding Visual Studio AI Web API Web Development