After a long while, I had to create a singleton service while I’m developing a feature for the ABP Framework. Then I decided to write an article about that: Why and how should we use the singleton pattern over a static class?
OK, I agree that creating singleton class or static class, doesn’t matter, is not a good practice. However, in practical, it can be unavoidable in some points. So, which one we should prefer? While the article title already answers this question, I will explain details of this decision in this article.
While this article uses C# as the programming language, the principles can be applied in any object oriented language.
You can get the source code from my GitHub samples repository.
Then you are lucky and you are doing a good thing, you can use the singleton lifetime.
ASP.NET Core allows you to register a class with the singleton lifetime. Assuming that you’ve a MyCache
class and you want to register it with the singleton lifetime. Write this inside the ConfigureServices
method of your Startup class
and that’s all:
services.AddSingleton<MyCache>();
You still need to care about multi-threading, you shouldn’t inject transient/scoped services into your singleton service (see my dependency injection best practices guide for more), but you don’t need to manually implement the singleton pattern. ASP.NET Core Dependency Injection system handles it. Whenever you need to the MyCache
service, just inject it like any other service.
However, there can be some reasons to manually implement the singleton pattern even if you use the dependency injection:
ConfigureServices
then you can not get benefit of the dependency injection.IServiceProvider
. For example, dependency injection may not be usable in an extension method.There are two main reasons that you should prefer singleton pattern over a static class. I will introduce briefly, then I will explain them in the next sections by details.
Singletons are well testable while a static class may not;
I created a solution that implements two caching services, one with the singleton pattern, the other one is a static class. I also created unit tests for both of the services:
You can get the source code from my GitHub samples repository. The rest of the article will be based on this solution.
Let’s see a simple caching class that is implemented via the singleton pattern:
public class SingletonCache
{
public static SingletonCache Instance { get; protected set; } = new SingletonCache();
private readonly IDictionary<string, object> _cacheDictionary;
protected internal SingletonCache()
{
_cacheDictionary = new Dictionary<string, object>();
}
public virtual void Add(string key, object value)
{
lock (_cacheDictionary)
{
_cacheDictionary[key] = value;
}
}
public virtual object GetOrNull(string key)
{
lock (_cacheDictionary)
{
if (_cacheDictionary.TryGetValue(key, out object value))
{
return value;
}
return null;
}
}
public virtual object GetOrAdd(string key, Func<object> factory)
{
lock (_cacheDictionary)
{
var value = GetOrNull(key);
if (value != null)
{
return value;
}
value = factory();
Add(key, value);
return value;
}
}
public virtual void Clear()
{
lock (_cacheDictionary)
{
_cacheDictionary.Clear();
}
}
public virtual bool Remove(string key)
{
lock (_cacheDictionary)
{
return _cacheDictionary.Remove(key);
}
}
public virtual int GetCount()
{
lock (_cacheDictionary)
{
return _cacheDictionary.Count;
}
}
}
Instance
property is the object that should be used by other classes, like SingletonCache.Instance.Add(...)
to add a new item to the cache.protected set
to make it settable/replaceable only by a derived class._cacheDictionary
is not static because the object (Instance
) is already static.protected internal
for the constructor because;protected
makes possible to inherit from this class.internal
makes possible to create an instance of this class from the same assembly or an allowed assembly. I allowed to the SingletonVsStatic.SingletonLib.Tests
project, so it can create an instance to test it (I used InternalsVisibleTo
attribute in the Properties/AssemblyInfo.cs
of the SingletonVsStatic.SingletonLib
project to make it possible).virtual
, so a derived class can override.#aspnetcore #csharp #singleton-pattern #design-patterns #best-practices #programming-c