Skip to content

Multiple ways to Implement the Singleton Design in C#

The singleton design pattern aids in limiting a class’s instantiation to a single object. One of the most despised design styles. Let’s take a look at what’s wrong with the pattern, how to fix it, and what we can learn from other singleton implementations.

Implementation Based on Lazy<T> Class

The previous code example can be significantly improved with a slight change — Lazy<T> class. The singleton object will only be instantiated when the Instance property is accessed.

				
					public sealed class Singleton
{
    private Singleton()
    {
        Console.WriteLine("Heavy initialization of the object here");
    }

    private static readonly Lazy<Singleton> _instance =
       new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance => _instance.Value;
}
				
			

Pros:

  • Thread-safe.
  • 100% lazy.
  • Ease of implementation.

Cons:

  • Again no extensibility at runtime.
  • Again the code is not testable.
  • Again SRP violation.
  • Again implicit dependencies issue.

Double-Checked Locking Implementation

In comparison to other singleton implementations, this one is my favorite because of its technical intricacy. The next code example is an exact match for the preceding one. However, from a learning sense, this is far more fascinating because it demonstrates how to utilize lock synchronization, the volatile keyword, and the double-checked locking approach.

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

    private Singleton() 
    {
        Console.WriteLine("Heavy initialization of the object is here");
    }

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

The locking construct makes the singleton implementation thread-safe, while double-checked locking is needed to improve performance. Acquiring a lock on line 17 is an expensive operation. There is no need to acquire a lock and then try to create a singleton if it has already been created. Having additional null check on line 15 helps to avoid unnecessary lock attempts, which is better for performance.

Pros:

  • Thread-safe.
  • 100% lazy.

Cons

  • Complicated implementation.
  • Again no extensibility in runtime.
  • Again the code is not testable.
  • Again SRP violation.
  • Again implicit dependencies issue.

Ambient Context

One of the major drawbacks of singleton is its lack of flexibility at runtime. We cannot replace the singleton class with ‘ExtendedSingleton’ or ‘SingletonMock’. If we need to achieve extensibility at runtime while keep having a global access point, we can use the ambient context pattern.

				
					public interface ISingletonInterface { }

public sealed class Singleton : ISingletonInterface
{ }

public sealed class SingletonMock : ISingletonInterface
{ }

public class SingletonContext
{
    public static ISingletonInterface Current { get; private set; }

    public static void Initialize(ISingletonInterface object)
    {
        Current = object;
    }
}

//Initialization in the composition root
SingletonContext.Initialize(new Singleton());

//Initialization for unit tests
SingletonContext.Initialize(new SingletonMock());
				
			

Pros:

  • Extensibility at runtime.
  • The single responsibility principle is now respected.

Cons:

  • This is not the singleton pattern anymore. Many instances of singleton classes can be created.
  • Implicit dependency issue is still present.

Singleton in IoC Container

Inversion of Control containers like Autofac control the lifetime of a registered object. All IoC containers have lifetime scope called singleton or similar. Any object can be registered as a single instance, so that the main goal of the singleton pattern will be achieved.

				
					public interface ISingleton { }

public sealed class Singleton : ISingleton
{
    public Singleton() 
    { 
        Console.WriteLine("Heavy initialization of the object here");
    }
}

...

//Somewhere in the composition root of the project:

var builder = new ContainerBuilder();
builder.RegisterType<Singleton>()
       .As<ISingleton>()
       .SingleInstance();
				
			

Pros:

  • Ease of implementation.
  • The code that uses “singleton” is unit testable now.
  • No SRP violation. Now the IoC container is responsible for creating the singleton, and not the singleton class itself.
  • The singleton class will be injected in the constructors of other classes, that is, used explicitly.

Cons:

  • Many instances can be created with new keyword.

Implementation Based on Static Property

Our first singleton implementation is quite common one and has just a few lines of code, but it works only to some degree. The implementation does allow creating a single instance, but not as lazy as it could be.

Let’s look at the code:

				
					public sealed class Singleton
{
    private Singleton()
    {
        Console.WriteLine("Heavy initialization of the object here");
    }
    
    private static readonly Singleton _instance = new Singleton();

    public static Singleton Instance => _instance;
}
				
			

There are two possible triggers here that could instantiate a singleton object. The first one is obvious and expected by us — access to the Instance property. But the second trigger is a little less obvious — it is access to any other static property of the singleton class. NET framework calls the static constructor of a class before accessing any of its static members for the first time. In our case, the singleton instantiation occurs inside a static constructor because this line:

private static readonly Singleton _instance = new Singleton();

is compiled to this code behind the scene:

static Singleton()
{
_instance = new Singleton();
}
private static readonly Singleton _instance;

So the current implementation will work to some extent. However, if the initialization of the singleton is to be as lazy as possible, you should consider using Lazy class.

Pros:

  • Thread-safe.
  • Ease of implementation.

Cons:

  • Not lazy.
  • The singleton object cannot be replaced with the another type of object at runtime in a polymorphic way.
  • The code that uses singleton is not unit testable.
  • The implementation violates the single responsibility principle because the class, in addition to doing its main work, is also responsible for creating itself.
  • Using a singleton will result in implicit dependencies in the rest of your code. I have already described the problem of implicit dependencies in my another article:

Conclusion

I don’t think there’s any compelling reason to use the conventional singleton anti-pattern. If it doesn’t have a state, it can still be a pattern (changing the state of one part of the application can affect the behavior of all dependent parts).

It’s used directly in the class constructor (private Singleton _singleton = Singleton.Instance). The usage of a singleton is not obscured by the code). It isn’t utilized in any code that has to be tested with unit tests. It can only be used with a small number of modules.

Leave a Reply

Your email address will not be published.