I can still remember many years ago going to a job interview that explicitly stated “We will ask you about patterns” and asking a co-worker what he thought I should read up on. To this day remember him rattling off patterns until he mentioned “singleton”, at which point I retorted “Is that even a pattern? I mean… It just kinda… is a thing… I guess?”. And by that I kind of meant that you probably use singletons every single day but you don’t really know it. Can it really be classified as a pattern?
Well, if we consult what is basically the bible in design patterns “Design Patterns: Elements of Reusable Object-Oriented Software “, we find that indeed, Singleton is in there. And so forever it stood in my bank of “interview answers” that I could have at the ready.
So What Is A Singleton?
Before we jump into specifics of .NET Core, let’s delve a bit deeper into what a singleton actually is. Trusty Wikipedia describes it as :
The singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.
Or, as I describe it to other developers :
When I run this app, while it’s alive, there should only ever be one instance of this object floating around. If two classes ever ask for this object, from startup to shutdown, it should always return the same instance.
It’s actually a pretty easy thing to understand. There is never really any questions about whether you are using “singletons” or not. If you use the “Mediator pattern” for example, people may say “Oh this is just XYZ pattern with(out) ABC”. But a singleton is simply “Is there one instance for the duration of the application? OK then it’s a singleton”.
Singletons In C#
Let’s push .NET Core specifically to one side and actually look at how we might achieve a singleton pattern in just C#. Again, Wikipedia kind of gives us some hints on how we might achieve this. It states :
- Declaring all constructors of the class to be private; and
- Providing a static method that returns a reference to the instance.
Seems easy enough, let’s have a go at that.
class MySingletonService { private static int creationCount = 0; private static readonly MySingletonService _mySingletonServiceInstance = new MySingletonService(); private MySingletonService() { creationCount++; } public static MySingletonService GetInstance() => _mySingletonServiceInstance; public int GetCreationCount() => creationCount; }
Here we have a class that has a private constructor. That means the only way it can be created is within itself, which we do with a static variable. The static variable by nature is the same for the lifetime of the app.
We also have a GetInstance() method that returns the value of the static variable. This is just exposing it to the outside world.
Finally, we have a “creationCount” which counts how many times the constructor is run. We want to use this to always be sure that the constructor is only ever run once (Of course we will also compare objects later).
Let’s quickly whip up something in a console app too :
static void Main(string[] args) { MySingletonService serviceInstance1 = MySingletonService.GetInstance(); Console.WriteLine(serviceInstance1.GetCreationCount()); MySingletonService serviceInstance2 = MySingletonService.GetInstance(); Console.WriteLine(serviceInstance2.GetCreationCount()); Console.WriteLine(serviceInstance1 == serviceInstance2); Console.ReadLine(); }
Running this we get…
1 1 True
Great! So we know that our constructor only ever ran once and when we compared the objects, it turned out they were the same! Awesome!
A Static Class Is Singleton Right?
The interesting thing about the above is that depending on what we actually need to do, the class itself may be static and we can simply refer to that rather than an instance instead. For example :
static class MySingletonService { public static string MyVariable { get; set; } }
With this, there is only ever going to be one instance of this class floating around (The static version). This is handy especially if you need to share “state” between classes without having to do the above with a creation of an instance that is basically static anyway.
Of course there are tradeoffs with this method and realistically, I only ever use it when it’s simple shared properties. But still in my opinion, it counts as a singleton.
Singleton Scope In .NET Core
Because .NET Core comes with it’s own packaged Dependency Injection, we can of course use dependency injection to handle our singleton for us. We do this by binding our class in a singleton scope in our ConfigureServices method like so :
services.AddSingleton<IMySingletonService, MySingletonService>();
Now, each time a instance of IMySingletonClass is asked for, it will return the same instance every time for the lifetime of your application. I mean… Pretty simple and not much to it right!
It’s also worth nothing that in earlier versions of .NET Core there was a method called “AddInstance”, this was essentially the same as AddSingleton, therefore in later versions it was simplified to just be AddSingleton with a pass in of the object. For example :
services.AddSingleton<IMySingletonService>(new MySingletonService())
When your application is fully “DI’d” then all of this makes sense and it’s hard to make a mistake. But if you still instantiate classes manually in your code, then you will need to be careful about people accidentally newing up this class elsewhere. Since you don’t have the whole “private constructor” thing going on, there isn’t that safety barrier to stop someone breaking your singleton pattern.
Dangers Of Singletons
Finally, I thought I would quickly touch on a couple of the biggest dangers of Singletons.
The first thing you need to ask yourself is, “Is it clear that your singleton class is a singleton to everyone else reading your code?”. One of the biggest mistakes that I see developers make is that they think that singleton should be the default state when infact it should be the opposite. A singleton should be a special usecase when the code explicitly calls for it. It’s like making static methods. Sure there are times when making things static “could” work, but you are just giving yourself more work down the road when it suddenly doesn’t seem so viable.
And lastly, remember that the state of your singleton is shared application wide for the entire lifetime of it. Not only that, your dependencies that you only instantiate once may have state that is also suddenly shared across your entire application. I can’t tell you the number of times someone has made a “service” a singleton only to find that suddenly across web requests it’s sharing data that shouldn’t be shared. I even talk about it here under “The Singleton Transient Trap“.
The TL;DR; is. Unless you have a good reason to make something a singleton. Don’t.
From my own experience it’s always better to not build a class as a singleton but configure it in the DI.
However, if for really strange reason you do not use DI in 2019, be very carefull about the fact that building a singleton with a static class could lead to multiple instanciation of that class in multi threading application.
So if you need to build your own singleton, you should prefer the use of Lazy.
example:
Yes. Good post. I really like the part about Dangers Of Singletons. What I also like is the first comment. Classic when you write a post about Singleton there is always someone to tell you should do DI and SP is not the better way. When doing ASP server I don’t see the problem of Singleton if used, as you mentioned, “special case”, and you are aware you should not transform it as a “service”. You singleton will be recreated for each request and this is what we want.
hmmm… Singleton will definitely not be recreated for every request and the same instance will be used across the threads and across the requests, that is precisely the danger of Singleton….
Thank you for your article. I was wondering whether singleton is concurrency safe unlike static classes. In other words, is a singleton shared between different users?
Depends what you mean by “safe”. Do you mean threadsafe? A singleton is certainly shared between users as by definition there is only one in existence for the lifetime of the app (No matter how many users are using the app)