Mastering the Singleton Design Pattern with Lazy Initialization in C#

Dhruv Tayal
3 min readSep 13, 2024

--

The Singleton design pattern is a creational pattern that ensures a class has only one instance and provides a global point of access to that instance. While the basic Singleton pattern is straightforward, combining it with lazy initialization in C# can lead to a more efficient and thread-safe implementation. In this blog post, we’ll explore how to implement a Singleton with lazy initialization in C#, discuss its benefits, and look at some potential pitfalls.

The Basic Singleton Pattern

First, let’s remind ourselves of a basic Singleton implementation:

public sealed class Singleton
{
private static Singleton instance = null;
private static readonly object padlock = new object();

private Singleton() {}

public static Singleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}

This implementation uses double-checked locking to ensure thread safety. However, it has some drawbacks:

  • The lock can become a bottleneck in high-concurrency scenarios.
  • It’s not as clean and simple as it could be.

Enter Lazy Initialization

C# provides the Lazy<T> class, which enables lazy initialization with full thread safety. Let's see how we can use it to improve our Singleton:

public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());

public static Singleton Instance { get { return lazy.Value; } }

private Singleton() {}
}

Let’s break down this implementation:

  • We use Lazy<Singleton> to wrap our Singleton instance.
  • The Lazy<T> constructor takes a delegate that defines how to create the instance.
  • The Instance property simply returns lazy.Value, which handles the thread-safe lazy initialization for us.

Benefits of Lazy Initialization

  1. Thread Safety: Lazy<T> is thread-safe by default. It ensures that only one thread can initialize the instance.
  2. Performance: The instance is only created when it’s first accessed, which can save resources if the Singleton is never used in a particular program execution.
  3. Simplicity: The code is much cleaner and easier to understand compared to the double-checked locking pattern.
  4. Flexibility: Lazy<T> allows for easy customization of the initialization process if needed.

Potential Gotchas

While this pattern is powerful, there are a few things to keep in mind:

  1. Serialization: If your Singleton needs to be serializable, you’ll need to implement serialization carefully to maintain the Singleton guarantee.
  2. Inheritance: The class is sealed to prevent inheritance, which could potentially create multiple instances.
  3. Disposal: If your Singleton implements IDisposable, you'll need to manage its lifecycle carefully.

Advanced Usage: Dependency Injection

In modern C# applications, especially those using dependency injection (DI) containers, you might want to register your Singleton with the DI container:

services.AddSingleton<Singleton>();

This approach allows the DI container to manage the lifecycle of your Singleton, which can be particularly useful in larger applications.

The Singleton pattern with lazy initialization in C# provides a clean, efficient, and thread-safe way to ensure a single instance of a class. By leveraging the Lazy<T> class, we can simplify our code while maintaining all the benefits of the Singleton pattern.

Remember, while Singletons can be useful, they should be used judiciously. They can make unit testing more difficult and, if overused, can lead to tightly coupled code. Always consider whether a Singleton is the best solution for your specific use case.

Happy coding!

--

--

Dhruv Tayal
Dhruv Tayal

Written by Dhruv Tayal

Experienced full-stack dev in Microsoft stack, specializing in back-end, web dev, databases, and APIs. Here to learn, share insights, and help others grow.

No responses yet