DEV Community

Devs Daddy
Devs Daddy

Posted on

Everything you need to know about Singleton in C# and Unity

Hey, everybody. If you are a C# developer or have programmed in any other language before, you must have heard about such a pattern as a Singleton.

Singleton is a generating pattern that ensures that only one object is created for a certain class and also provides an access point to this object. It is used when you want only one instance of a class to exist.

In this article, we will look at how it should be written in reality and in which cases it is worth modernizing.

Example of Basic (Junior) Singleton:

public class MySingleton {
    private MySingleton() {}
    private static MySingleton source = null;

    public static MySingleton Main(){
        if (source == null)
            source = new MySingleton();

        return source;
    }
}
Enter fullscreen mode Exit fullscreen mode

There are various ways to implement Singleton in C#. I will list some of them here in order from worst to best, starting with the most common ones. All these implementations have common features:

  • A single constructor that is private and without parameters. This will prevent the creation of other instances (which would be a violation of the pattern).
  • The class must be sealed. Strictly speaking this is optional, based on the Singleton concepts above, but it allows the JIT compiler to improve optimization.
  • The variable that holds a reference to the created instance must be static.
  • You need a public static property that references the created instance.

So now, with these general properties of our singleton class in mind, let's look at different implementations.

№ 1: No thread protection for single-threaded applications and games

The implementation below is not thread-safe - meaning that two different threads could pass the if (source == null) condition by creating two instances, which violates the Singleton principle. Note that in fact an instance may have already been created before the condition is passed, but the memory model does not guarantee that the new instance value will be visible to other threads unless appropriate locks are taken. You can certainly use it in single-threaded applications and games, but I wouldn't recommend doing so.

public sealed class MySingleton
{
    private MySingleton() {}
    private static MySingleton source = null;

    public static MySingleton Main
    {
        get
        {
            if (source == null)
                source = new MySingleton();

            return source;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Mono Variant #1 (For Unity):

public sealed class MySingleton : MonoBehaviour
{
    private MySingleton() {}
    private static MySingleton source = null;

    public static MySingleton Main
    {
        get
        {
            if (source == null){
                GameObject singleton = new GameObject("__SINGLETON__");
                source = singleton.AddComponent<MySingleton>();
            }

            return source;
        }
    }

    void Awake(){
        transform.SetParent(null);
        DontDestroyOnLoad(this);
    }
}
Enter fullscreen mode Exit fullscreen mode

№2: Simple Thread-Safe Variant

public sealed class MySingleton
{
    private MySingleton() {}
    private static MySingleton source = null;
    private static readonly object threadlock = new object();

    public static MySingleton Main
    {
        get {
            lock (threadlock) {
                if (source == null)
                    source = new MySingleton();

                return source;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This implementation is thread-safe because it creates a lock for the shared threadlock object and then checks to see if an instance was created before the current instance is created. This eliminates the memory protection problem (since locking ensures that all reads to an instance of the Singleton class will logically occur after the lock is complete, and unlocking ensures that all writes will logically occur before the lock is released) and ensures that only one thread creates an instance. However, the performance of this version suffers because locking occurs whenever an instance is requested.

Note that instead of locking typeof(Singleton) as some Singleton implementations do, I lock the value of a static variable that is private within the class. Locking objects that can be accessed by other classes degrades performance and introduces the risk of interlocking. I use a simple style - whenever possible, you should lock objects specifically created for the purpose of locking. Usually such objects should use the modifier private.

Mono Variant #2 for Unity:

public sealed class MySingleton : MonoBehaviour
{
    private MySingleton() {}
    private static MySingleton source = null;
    private static readonly object threadlock = new object();

    public static MySingleton Main
    {
        get
        {
            lock (threadlock) {
                if (source == null){
                   GameObject singleton = new GameObject("__SINGLETON__");
                   source = singleton.AddComponent<MySingleton>();
                }

                return source;
            }
        }
    }

    void Awake(){
        transform.SetParent(null);
        DontDestroyOnLoad(this);
    }
}
Enter fullscreen mode Exit fullscreen mode

№3: Thread-Safety without locking

public sealed class MySingleton
{
    static MySingleton() { }
    private MySingleton() { }
    private static readonly MySingleton source = new MySingleton();

    public static MySingleton Main
    {
        get
        {
            return source;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, this is indeed a very simple implementation - but why is it thread-safe and how does lazy loading work in this case? Static constructors in C# are only called to execute when an instance of a class is created or a static class member is referenced, and are only executed once for an AppDomain. This version will be faster than the previous version because there is no additional check for the value null.

However, there are a few flaws in this implementation:

  • Loading is not as lazy as in other implementations. In particular, if you have other static members in your Singleton class other than Main, accessing those members will require the creation of an instance. This will be fixed in the next implementation.
  • There will be a problem if one static constructor calls another, which in turn calls the first.

№4: Lazy Load

public sealed class MySingleton
{
    private MySingleton() { }
    public static MySingleton Main { get { return Nested.source; } }

    private class Nested
    {
        static Nested(){}
        internal static readonly MySingleton source = new MySingleton();
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the instance is initiated by the first reference to a static member of the nested class, which is only used in Main. This means that this implementation fully supports lazy instance creation, but still has all the performance benefits of previous versions. Note that although nested classes have access to private members of the upper class, the reverse is not true, so the internal modifier must be used. This does not cause any other problems, since the nested class itself is private.

№5: Lazy type (.Net Framework 4+)

If you are using version .NET Framework 4 (or higher), you can use the System.Lazy type to implement lazy loading very simply.

public sealed class MySingleton
{
    private MySingleton() { }
    private static readonly Lazy<MySingleton> lazy = new Lazy<MySingleton>(() => new MySingleton());
    public static MySingleton Main { get { return lazy.Value; } }            
}
Enter fullscreen mode Exit fullscreen mode

This is a fairly simple implementation that works well. It also allows you to check if an instance was created using the IsValueCreated property if you need to.

№6: Lazy Singleton for Unity

public abstract class MySingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static readonly Lazy<T> LazyInstance = new Lazy<T>(CreateSingleton);

    public static T Main => LazyInstance.Value;

    private static T CreateSingleton()
    {
        var ownerObject = new GameObject($"__{typeof(T).Name}__");
        var instance = ownerObject.AddComponent<T>();
        DontDestroyOnLoad(ownerObject);
        return instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

This example is thread-safe and lazy for use within Unity. It also uses Generic for ease of further inheritance.

In conclusion

As you can see, although this is a fairly simple pattern, it has many different implementations to suit your specific tasks. Somewhere you can use simple solutions, somewhere complex, but do not forget the main thing - the simpler you make something for yourself, the better, do not create complications where they are not necessary.

Thanks!


My Discord | My Blog | My GitHub | Buy me a Beer

Top comments (0)