C# Design Patterns

C# Design Patterns — Singleton

Providing one instance for the whole application

Andre Lopes
The Startup

--

Photo by Hitesh Choudhary on Unsplash

Design patterns are common coding practices defined to solve common software development problems.

The Singleton pattern was developed to provide a common way of providing a single instance of an object throughout the whole application lifetime. So, as long as the application is not restarted, this instance must be the same regardless of how many times you instantiate it.

Usage and Drawbacks

Examples

Some examples of singletons are objects that need to share resources between classes or threads, like:

  • Global state management
  • Logging service
  • .NET Core AppSettings
  • And others…

Drawbacks

Sometimes, with poor implementation, the Singleton pattern can actually become an anti-pattern , the reasons are:

  • It is really difficult to test if you are not using Dependency Injection, because it is statically created, so you can’t manually control it, as a result, you can’t mock it.
  • It can lead to memory leaks if dependencies are not properly disposed after usage

Let’s build it

Let’s imagine that we have a service interface IGreetingService.cs:

Now it was required that this service should not change throughout the whole application.

The classic approach

There are many ways to implement the Singleton pattern in C#.

Here I’ll show you three approaches and which one I would use.

Double-Checked Locking

I didn’t consider showing this approach without thread lock because it is very unsafe to use in multithreaded applications that way.

Here we have

  • Two private variables: a static variable that is the service instance, a read-only object that is going to work as the thread lock.
  • A private constructor to prevent a new service from being manually instantiated.
  • A public and static property that is how we can access our singleton instance.

You can note that we have two null checks. The inner check is to prevent the instance from being recreated and the outer null check is for preventing lock every time we need to access the instance, thus increasing performance.

Also, we have the lock for preventing this code from being run by more than one thread at the same time, making it thread-safe.

Now if you execute it like this:

IGreetingService service = DoubleCheckedLockingGreetingService.Instance;
IGreetingService service_2 = DoubleCheckedLockingGreetingService.Instance;
service.Greet("Singleton");
service_2.Greet("Singleton");

You will get the same output for both methods.

Lazy<T>

A second approach is letting the instance be created for the first time it is requested.

.NET has the Lazy<T> class, which provides a lazy initialization of objects for us.

Now if you execute it like this:

IGreetingService service = LazyObjectGreetingService.Instance;
IGreetingService service_2 = LazyObjectGreetingService.Instance;
service.Greet("Singleton");
service_2.Greet("Singleton");

You will get the same output for both methods.

Object Eager Initialization

In C# there’s an option of assigning the instance for the static variable, making it possible for the object to be initialized when it is first needed.

We can go even further and not use a private variable anymore and use Auto-Property, which assigns a value for the property if it doesn’t have a value yet.

Note the static constructor. This is needed so the C# compiler will not mark the type as beforefieldinit. This will guarantee the class laziness.

Now if you execute it like this:

IGreetingService service = SimpleGreetingService.Instance;
IGreetingService service_2 = SimpleGreetingService.Instance;
service.Greet("Singleton");
service_2.Greet("Singleton");

You will get the same output for both methods.

Modern .NET Dependency Injection

Modern .NET/.NET Core applications already come with a built-in dependency injection mechanism that automatically injects services with the respective life-cycle they need.

  • Transient — Injects a new instance every time it is created and lives as long as the parent
  • Scoped — Injected once per request and lives as long as the request lives
  • Singleton — Injected once per application and lives as long as the application lives

So, in a .NET Core WebAPI, for example, you only need to register the IGreetingService with the GreetingService as a Singleton in the ConfigureServices method in your Startup.cs file. Like:

services.AddSingleton<IGreetingService,GreetingService>();

And for the implementation of this service we have:

Note that we don’t need an Instance accessor property to access our singleton. This happens because we delegate the job of assigning this instance to the framework, so all you need to do is inject the IGreetingService where you need it and .NET will provide you the only instance it created. For example:

public MyClass (IGreetingService service)
{
// Do something or assign to a class member
}

Conclusion

You could see how easy is to implement the Singleton pattern from scratch with C#. Even though it is a pattern and has its applicabilities, it needs to be used with care because it can lead to many system issues like memory leaks. Also, due to many drawbacks, it is often recommended to not use the Singleton pattern because it can become an anti-pattern.

Thankfully, with the .NET dependency injection mechanism, many drawbacks can be avoided, like the difficulty of implementing unit tests.

I uploaded the code for the normal implementation in this repository.

--

--

Andre Lopes
The Startup

Full-stack developer | Casual gamer | Clean Architecture passionate