Introduction

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 we should 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.

Using Dependency Injection?

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:

  • ASP.NET Core Dependency Injection system doesn't allow to use services until the service registration phase completes. If you need to use your service before or inside the ConfigureServices then you can not get benefit of the dependency injection.
  • You can't inject a service from a static context where you don't have access to the IServiceProvider. For example, dependency injection may not be usable in an extension method.

Singleton Pattern vs Static Class

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.

Testability

Singletons are well testable while a static class may not;

  • If your class stores state (data), running multiple tests might effect each other, so writing test will be harder.
  • Static classes are hard or impossible to mock. So, if you are testing a class depends on the static class, mocking may not be an easy option.

Extensibility

  • It is not possible to inherit from a static class, while it is possible with singleton pattern if you want to allow it. So, anyone can inherit from a singleton class, override a method and replace the service.
  • It is not possible to write an extension method to a static class while it is possible for a singleton object.

The Solution Overview

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:

solution-overview.png

You can get the source code from my GitHub samples repository. The rest of the article will be based on this solution.

Implementing a Singleton Service

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;
        }
    }
}
  • The static Instance property is the object that should be used by other classes, like SingletonCache.Instance.Add(...) to add a new item to the cache.
    • I marked the setter as protected set to make it settable/replaceable only by a derived class.
  • The _cacheDictionary is not static because the object (Instance) is already static.
  • I declared 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).
  • I make all methods virtual, so a derived class can override.

Extending the SingletonCache

Creating an Extension Method

Anyone can write an extension method for the SingletonCache class.

Example: You found that you are frequently repeating yourself for such a code block:

var myValue = SingletonCache.Instance.GetOrNull("MyKey");
if (myValue == null)
{
    throw new ApplicationException("MyKey was not present in the cache!");
}

//...continue to your logic if it is not null

It would be better that you have a GetOrThrowException method that throws exception if given key is not present:

var myValue = SingletonCache.Instance.GetOrThrowException("MyKey");
//...continue to your logic if it is not null

You can easily create such an extension method to implement it yourself:

public static class MySingletonCacheExtensions
{
    public static object GetOrThrowException(
        this SingletonCache singletonCache,
        string key)
    {
        var value = singletonCache.GetOrNull(key);

        if (value == null)
        {
            throw new ApplicationException(
                "Given key was not present in the cache: " + key);
        }

        return value;
    }
}

That's not possible if it was a static class.

Deriving from the SingletonCache

If SingletonCache is in a library that you are using, you can't change it source code. But you can inherit and override a method if you like.

Example: You see that the Add method allows to add null values to the cache, which may not a good thing for your application. You can inherit from the SingletonCache and override the Add method:

public class MySingletonCache : SingletonCache
{
    public static void Replace()
    {
        SingletonCache.Instance = new MySingletonCache();
    }

    public override void Add(string key, object value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }

        base.Add(key, value);
    }
}

Then you need to call MySingletonCache.Replace() method at the beginning of your application to replace the SingletonCache.Instance with your own implementation.

Testing the SingletonCache

Testing the SingletonCache is easy. I used xUnit for tests. xUnit create a new instance of the test class for each test method, so I don't care if tests effect each other: Every test uses a different cache object which is initialized to the same initial state (added two sample values in the constructor). I can even run all tests in parallel, no problem!

public class SingletonCache_Tests
{
    private readonly SingletonCache _singletonCache;

    public SingletonCache_Tests()
    {
        _singletonCache = new SingletonCache();

        _singletonCache.Add("TestKey1", "TestValue1");
        _singletonCache.Add("TestKey2", "TestValue2");
    }

    [Fact]
    public void Should_Contain_Initial_Values()
    {
        Assert.Equal(2, _singletonCache.GetCount());

        Assert.Equal("TestValue1", _singletonCache.GetOrNull("TestKey1"));
        Assert.Equal("TestValue2", _singletonCache.GetOrNull("TestKey2"));
    }

    [Fact]
    public void Should_Add_And_Get_Values()
    {
        _singletonCache.Add("MyNumber", 42);

        Assert.Equal(42, _singletonCache.GetOrNull("MyNumber"));
    }

    [Fact]
    public void Should_Increase_Count_When_A_New_Item_Added()
    {
        Assert.Equal(2, _singletonCache.GetCount());

        _singletonCache.Add("TestKeyX", "X");

        Assert.Equal(3, _singletonCache.GetCount());
    }

    [Fact]
    public void Clear_Should_Delete_All_Values()
    {
        _singletonCache.Clear();

        Assert.Equal(0, _singletonCache.GetCount());
        Assert.Null(_singletonCache.GetOrNull("TestKey1"));
    }

    [Fact]
    public void Should_Remove_Values()
    {
        _singletonCache.Remove("TestKey1");

        Assert.Null(_singletonCache.GetOrNull("TestKey1"));
    }

    [Fact]
    public void Should_Use_Factory_Only_If_The_Value_Was_Not_Present()
    {
        //The key is already present, so it doesn't use the factory to create a new one
        Assert.Equal("TestValue1",
        _singletonCache.GetOrAdd("TestKey1", () => "TestValue1_Changed"));

        _singletonCache.Remove("TestKey1");

        //The key is not present, so it uses the factory to create a new one
        Assert.Equal("TestValue1_Changed",
        _singletonCache.GetOrAdd("TestKey1", () => "TestValue1_Changed"));
    }

    [Fact]
    public void GetOrThrowException_Should_Throw_Exception_For_Unknown_Keys()
    {
        Assert.Throws<ApplicationException>(() =>
        {
            _singletonCache.GetOrThrowException("UnknownKey");
        });
    }
}

Implementing an Interface?

If you are developing a reusable class library, it would be a good idea to create an ISingletonCache interface that is implemented by the SingletonCache class. In this way, library users can replace your service without requiring to inherit from it, they can just re-implement the interface.

Implementing a Static Class

The same singleton class could be implemented as a static class like shown below:

public static class StaticCache
{
    private static readonly IDictionary<string, object> _cacheDictionary;

    static StaticCache()
    {
        _cacheDictionary = new Dictionary<string, object>();
    }

    public static void Add(string key, object value)
    {
        lock (_cacheDictionary)
        {
            _cacheDictionary[key] = value;
        }
    }

    public static object GetOrNull(string key)
    {
        lock (_cacheDictionary)
        {
            if (_cacheDictionary.TryGetValue(key, out object value))
            {
                return value;
            }

            return null;
        }
    }

    public static 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 static void Clear()
    {
        lock (_cacheDictionary)
        {
            _cacheDictionary.Clear();
        }
    }

    public static bool Remove(string key)
    {
        lock (_cacheDictionary)
        {
            return _cacheDictionary.Remove(key);
        }
    }

    public static int GetCount()
    {
        lock (_cacheDictionary)
        {
            return _cacheDictionary.Count;
        }
    }
}

Extending the StaticCache

  • No way to inherit and override a method.
  • No way to create an extension method.

Testing the StaticCache

Testing the StaticCache is possible. However, your tests may easily effect each other since they all will be executed in the same object and you can not control the execution order of the tests.

I've created a test class, StaticCache_Tests, inside the solution. I will partially share it here (you can see the full source code):

public class StaticCache_Tests
{
    static StaticCache_Tests()
    {
        StaticCache.Add("TestKey1", "TestValue1");
        StaticCache.Add("TestKey2", "TestValue2");
    }

    [Fact]
    public void Should_Contain_Initial_Values()
    {
        Assert.Equal(2, StaticCache.GetCount());

        Assert.Equal("TestValue1", StaticCache.GetOrNull("TestKey1"));
        Assert.Equal("TestValue2", StaticCache.GetOrNull("TestKey2"));
    }

    [Fact]
    public void Should_Add_And_Get_Values()
    {
        StaticCache.Add("MyNumber", 42);

        Assert.Equal(42, StaticCache.GetOrNull("MyNumber"));
    }

    [Fact]
    public void Should_Increase_Count_When_A_New_Item_Added()
    {
        Assert.Equal(2, StaticCache.GetCount());

        StaticCache.Add("TestKeyX", "X");

        Assert.Equal(3, StaticCache.GetCount());
    }
    
    //...other tests
}

When I run all these tests together (parallel or not, doesn't matter since I can't control the execution order) some tests randomly fail:

failing-tests.png

When I run one of the failing tests (without running others) it passes. But they can't properly work together.

Even if I would be able to control the execution order of the test methods, I must consider the result of the previous tests when I write a new test method. That means any change in a test method may effect next test methods. Also, this makes impossible to run all tests in parallel which is very important to reduce the total test time.

About Multi-Threading

Use singleton or static class, doesn't matter, you need to care about multi-threading since multiple threads (requests in web application) may use your service concurrently.

Using lock statements while accessing the shared resources is one way to overcome this problem. You can use other techniques based on your performance and functional requirements. For example, using a ReaderWriterLockSlim would have a better performance for such a cache class.

Conclusion

  • Use Dependency Injection with singleton lifetime as a best practice wherever possible.
  • Use singleton pattern rather than static classes. Using a singleton has no additional cost for you while it has important advantages.