Thread-safe singletons in .NET
Lets take a look at (static) constructors, thread-safety and static singletons.
Field initialization
Let's start by examining how field initialization works in .NET.
Let's say you have the following code:
public class Program
{
// field declaration and initialization
private int i = 5;
public Program ()
{
// do ctor logic here
}
}
Even when you initialize a field at the top of your class (simultaneously declaring it as well), internally the compiler moves that initialization to the constructor.
Like this:
public class Program
{
// declaration
private int i;
public Program ()
{
// initialization
i = 5;
}
}
Static fields are no different. Their initialization is also moved into the static constructor (aka the type constructor aka class constructor). You will rarely need static constructors though, most of the time you will just initialize fields when declaring them.
So, (hopefully) no real magic at the moment.
Static constructors
Static constructors behave a bit different than 'normal' constructors.
For one thing, type constructors are only called when you use the type for the very first time. They use lazy evaluation and you have no control over when they are called.
Static constructors are always thread safe. Well, they actually aren't really, but we can regard them as such. The runtime guarantees that a static constructor is only called once. So even if a type is called by multiple threads at the same time, the static constructor is always executed one time.
To get a better understanding how this works, it helps to know what purpose it serves.
The JIT (Just in Time) compiler compiles types when they are needed and referenced in code. It will construct a 'type object' in memory for that class, which contains all of the static members, compiled methods (both instance and static) and compiled versions of the constructors for instance objects. The static constructor is replaced when JIT compilation occurs. Which automatically means static constructors can only be executed once, and only once. You can't call something again that isn't there anymore :).
Be careful!
Only the initialization of static fields are thread safe, the value is not! To guarantee thread safety on static fields, add readonly (read initonly). This guarantees that the value cannot be overwritten (if you are not using a collection of some sort that is).
Static singleton
So, with this knowledge, we now have a very simple way of creating a thread-safe singleton:
public sealed class Singleton
{
// private ctor so no-one can create an instance
private Singleton() {}
// static field to hold the singleton
// initialization is thread-safe because it is handled by the static ctor
private readonly static Singleton instance = new Singleton();
// public static singleton instance
public static Singleton Instance
{
get { return instance; }
}
}