DEV Community

Cover image for Singleton Design Pattern in C#: Full Guide
ByteHide
ByteHide

Posted on • Originally published at bytehide.com

Singleton Design Pattern in C#: Full Guide

Have you ever wondered how to maintain a unique instance throughout the lifecycle of an application and ensure that one class only has one instance?

Here’s the magic word for you – Singleton. Specifically, we’ll explore each twist and turn of the Singleton journey in C#. Ready to dive in? Let’s get started!

Understanding Singleton in C#

Singleton, as a design pattern, is quite the stalwart in the world of Object-Oriented Programming languages and definitely in C#. Let’s dig a little deeper into the rich soil of this concept and see what gems we can unearth.

Concept of Singleton Class in C#

A singleton is like a deck of cards; handy, compact, but up there can only be one Ace of Spades. Singleton classes are of utmost importance when only one instance of a class is to be created. In the bustling city of C# development, this intelligent design model finds usage in multiple scenarios – be it representing a single DB connection shared by multiple objects or reading a configuration file.

public sealed class Singleton
{
    private static Singleton singleton = null;
    private static readonly object singletonLock = new object();

    Singleton() {}

    public static Singleton SingleInstance
    {
        get
        {
            lock (singletonLock)
            {
                if (singleton == null)
                {
                    singleton = new Singleton();
                }
                return singleton;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Got to say, C# sure makes things neat. In the above code snippet, we’ve got a Singleton class. Notice the sealed keyword? That’s how we ensure no other class can inherit from our Singleton, keeping it truly one-of-a-kind.

The SingleInstance method here acts like the gatekeeper, loyally providing only one instance of our Singleton, keeping sure it is safely locked and thread-safe. More on thread safety later.

Now, imagine you have a chocolate stash you don’t want to share (who wants to share chocolate, right?). Our Singleton class behaves kind of like that. By creating only one instance, it safeguards resources by keeping that instance locked away, only accessible via its very own property SingleInstance.

But let’s simplify this and get a better grip of Singleton by crafting another basic, yet illustrative, application.

Mechanism of Singleton Class

Singleton class, in technical terms, is a way to provide one and only one object of a particular type. But let’s illustrate this with an example that we can all relate to. Imagine you’re at a movie theatre. There’s only one projector that plays the movie, right? That’s exactly what Singleton is.

Let’s code a Projector Singleton class.

public sealed class Projector
{
    // Private object instance of the class initialized to null
    private static Projector instance = null;
    // Object for lock functionality
    private static readonly object lockObject = new object();

    // Private constructor ensures instances can't be made elsewhere
    private Projector() {}

    // Property to get the instance of Projector
    public static Projector Instance
    {
        get
        {
            // Locking the block of code where instance is created
            lock (lockObject)
            {
                // This will be null the first time this piece of code runs
                if(instance == null)
                {
                    instance = new Projector();
                }
                return instance;
            }
        }
    }

    // Some projector method
    public void Display()
    {
        Console.WriteLine("Displaying movie");
    }
}
Enter fullscreen mode Exit fullscreen mode

In this analogy, we have a Singleton Projector class. Any cinema in our application trying to play a movie has to access this single projector instance. They can’t get another projector, or create a new one; there’s only one projector available, and the Singleton Design Pattern ensures that. Here’s how we call it:

// Get the singleton instance
Projector projector = Projector.Instance;

// Call any of its methods
projector.Display();
Enter fullscreen mode Exit fullscreen mode

Just like in any theatre, once the movie starts playing, every audience is watching the same one. And our Singleton Projector ensures that.

Exploring Singleton Design Pattern in C#

So, you’ve heard about Singleton Design Pattern, right? We’re going to dive deeper into this fascinating topic right now. Just like a car, Singleton Design Pattern takes tiny pieces (our Singleton class) and builds a vehicle that drives our codes with efficiency and pace. Singletons ensure that we have one, and only one instance of a class throughout our application.

Application Scenarios of Singleton Design Pattern

Singleton Design Pattern is a useful tool in many scenarios, kind of like a Swiss Army Knife. Think of it like this, whenever a class needs to have only one instance, and this instance requires global access, Singleton comes to the rescue. Now that’s a superhero!

From logging services, thread pools, device drivers to configuration settings, and caching, Singleton powers an impressive range of applications. Let’s throw some light on how it helps with logging.

public sealed class Logger
{
    private static Logger instance = null;
    private static readonly object padlock = new object();

    Logger() {}

    public static Logger Instance
    {
        get
        {
            lock(padlock)
            {
                if(instance == null)
                {
                    instance = new Logger();
                }
                return instance;
            }
        }
    }
    public void LogMessage(string message)
    {
        //Code to log message
    }
}
Enter fullscreen mode Exit fullscreen mode

In this code, we’re creating a Logger singleton. Imagine the Logger as a note-taker who writes down events happening in your classroom (program). You want to make sure there is only one note-taker. Otherwise, the notes might differ and cause confusion. The same principle applies here. Through Singleton, only one instance of Logger takes care of logging.

Now let’s introduce another Singleton instance, ‘Database Connection’. It’s costly and time-consuming to keep opening and closing database connections. Here comes our Singleton class to the rescue, manage only one connection at a time!

public sealed class SingletonDbConnection
{
    private static SingletonDbConnection instance = null;

    private SingletonDbConnection() {}

    public static SingletonDbConnection Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new SingletonDbConnection();
            }
            return instance;
        }
    }
    public void RunQuery(string query)
    {
        //Code to execute the SQL query
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, Singleton ensures that only one database connection is active at a time, just like how using a single pen can help you maintain neat handwriting throughout your notes.

Adapting Adapter Pattern in C#

Sometimes, you might come across classes that are not compatible with each other, kind of like trying to put a square peg in a round hole. Adapter Pattern acts as a link to make these classes work together.

Adapter Pattern Vs Singleton Design Pattern

It’s easy to mix up Adapter pattern with Singleton. They’re both design patterns, but they are as different as chalk and cheese. While Adapter Pattern is focused on making different classes work together, Singleton Design Pattern ensures only one instance of a class exists.

Imagine the Adapter Pattern as a universal travel adapter that lets you use devices from different countries in one socket. On the other hand, Singleton is like that one golden ticket in a chocolate bar—there’s only one, but everyone can use it!

So, when should you use one over the other? You’d reach for the Adapter Pattern when you need to make incompatible interfaces work together, kind of like using a USB-C to USB-A adapter to connect your modern laptop with older devices. Singleton Pattern is more like your WiFi connection, there’s only one signal but everybody can use it (up to its capacity, of course).

The Composition of a C# Singleton Class

Imagine, if you will, a pizza. A pizza is great, but it takes more than just crust to make a good one, right? We need toppings, lots of them! Similarly, a Singleton class is comprised of several key ‘ingredients’ or elements. Let’s see how this pizza analogy applies to our C# Singleton class, piece by piece.

Breaking Down the Structure of Singleton Class

Just like how a delicious pizza has the right balance of crust, sauce, cheese, and toppings, a Singleton class in C#, at its core, is made up of three main components.

  1. A private static instance of the class itself.
  2. A private constructor.
  3. A public static method that returns the instance of the class.

Let’s look at this in the context of a simple code example.

public sealed class Singleton
{
    private static Singleton instance = null;  // Private Instance
    private Singleton() {} // Private Constructor 

    public static Singleton Instance  // Public Method
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Think of it this way, the static instance is like the pizza crust, acting as the base of our class. The private constructor is like the sauce. It’s a key component, but you don’t interact with it directly when you eat the pizza. Finally, the public static Instance method is like the cheese – it’s what you interact with and helps hold everything together.

The null is initially assigned to the instance. When the Instance method is called for the very first time, it creates a new Singleton object. But for any subsequent requests, it’s like asking for a slice of already baked pizza – you simply return the same slice (or instance) you had before.

C# Singleton: A Practical Example

Think of a printer. Ideally, in an office, you would want all print requests to go to a single, centralized printer. There should only be one instance of a printer, and everyone gets their print jobs from this single source.

Code Walkthrough: Implementing Singleton

Let’s code this out and see how it works.

public sealed class Printer
{
    private static Printer instance = null; // This is our single printer. Initially, it doesn't exist ('null')

    private Printer() {} // This is where we set up our printer. But, note that it's private. 
    // Only this Printer class can setup the printer

    public static Printer Instance // This is how people request to use the printer
    {
        get
        {
            // This part checks if the printer exists.
            if (instance == null)
            {
                // If it doesn't, it sets up a new printer
                instance = new Printer();
            }
            // Whether it just set up a new printer or one already existed, it returns the printer
            return instance;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Just like requesting a pizza slice, whenever you want to print something, you call Printer.Instance. No matter how many times you call it, you are always returned the same, unique, single printer. It doesn’t create the printer anew for every request, but instead, it reuses the pre-existing one. Neat, huh?

It’s as simple as that! Want to print something? Call Printer.Instance and you’ll get the single, unique printer ready to serve your documents.

The Importance of Thread Safety in C# Singleton

Thread safety in Singleton design is a crucial cornerstone to grasp. It’s like making sure there’s only one captain steering the ship, no matter how choppy the waters!

Why Thread-Safe Singleton is a Must

In the world of programming, more than one thread trying to access a resource simultaneously may result in what we call a race condition – a chaos equivalent to letting two drivers control a car simultaneously! Therefore, when multiple threads could potentially create multiple instances of our singleton class, it violates the fundamental principle of the Singleton pattern – one and only one instance!

public sealed class ThreadSafeSingleton
{
    private static ThreadSafeSingleton instance = null;
    private static readonly object lockCheck = new object();

    private ThreadSafeSingleton(){}

    public static ThreadSafeSingleton Instance
    {
        get
        {
            lock(lockCheck)
            {
                if (instance == null)
                {
                    instance = new ThreadSafeSingleton();
                }
                return instance;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this simple yet effective code example, the ‘lock’ keyword is like the bouncer at the entrance of a club, admitting one person in (or one thread into the block of code) at a time. Even when multiple threads try to access it at once, only one gets through making sure the instance, once created, is the one and only.

Fine Tuning Thread Safe Singleton, The Double-Check Locking

Let’s make this example even better. Double-check locking is a technique that further optimizes our thread-safe Singleton.

public sealed class ThreadSafeSingleton
{
    private static ThreadSafeSingleton instance = null;
    private static readonly object lockCheck = new object();

    private ThreadSafeSingleton(){}

    public static ThreadSafeSingleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock(lockCheck)
                {
                    if (instance == null)
                    {
                        instance = new ThreadSafeSingleton();
                    }
                }
            }
            return instance;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Twist in the plot! Why the double check, you ask? Because we want to avoid the costly lock operation when the instance isn’t null which would be most of the times after the initial few calls.

The .NET way: Lazy

But hold on, we’re in the world of C#, and there’s a simpler, more effective way to keep our Singleton thread-safe! The .NET Framework provides Lazy<T> that makes laziness a virtue!

public class Singleton
{
    private static readonly Lazy<Singleton> lazyInstance =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton(){}

    public static Singleton Instance
    {
        get
        {
            return lazyInstance.Value;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Lazy<T> provides support for lazy initialization. When multiple threads try to access it, Lazy<T> ensures that only one of them can call the Singleton constructor. And the rest simply wait patiently and are then allowed to carry on using the instance once it’s been created.

Dependency Injection and C# addSingleton

Dependency Injection, you might compare it to a castle’s foundation. Just as a builder ensures that the foundation of a castle is solid so that the rest of the construction can be successful, Dependency Injection provides a solid base for our objects.

Let’s think of it like this – it works like your friendly neighborhood mailperson, delivering right what is needed (or should we say…dependencies)! Its job? It gives an object all the other objects it needs to do its job! So, how does addSingleton fit into this picture?

Think of addSingleton as our super special delivery guy who ensures everyone gets exactly the same copy of today’s hot off the press newspaper. Once addSingleton delivers a dependency, it stays consistent throughout the application run! Now, isn’t that neat?

Introduction to Dependency Injection and addSingleton

Imagine having a town mayor (also known as our application). This mayor interacts with many other townsfolk (also known as classes or objects), and she is very popular, so she doesn’t go to them directly. Instead, she has a message carrier who relays the news (or…dependencies) to everyone. This carrier is what we call Dependency Injection (DI).

DI follows a simple principle – it keeps related things together and unrelated things apart. It reduces the dependent code, makes the code more manageable, testable, and promotes loose coupling which is a good coding practice.

Now, the Singleton method is a unique pattern where an object is created only once – meaning everyone gets the same copy, and we use the AddSingleton method of .NET Core to ensure this.

Let’s get a bit practical and let C# do the talking!

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMyService, MyService>();
}
Enter fullscreen mode Exit fullscreen mode

This snippet is a shiny example of AddSingleton. The AddSingleton function tells the application to build an object one time and one time only, and use the same instance for every future request.

Consider IMyService like a newspaper and AddSingleton as our special delivery guy, delivering IMyService everywhere in the city (our application). Every time anyone asks for IMyService, they get MyService delivered to them, fresh from the press!

It’s like saying, “Hey! If anyone asks for IMyService, give them MyService, and always give them the same one!”

Now let’s extend our notion with another example. Imagine you have an application where you need to access a database (IDatabaseService) to get data.

public interface IDatabaseService
{
    IEnumerable<string> GetData();
}

public class DatabaseService : IDatabaseService
{
    public IEnumerable<string> GetData()
    {
        //Add code here to retrieve data from database
        return new List<string> {"Sample Data"};
    }
}

public class HomeController : Controller
{
    private readonly IDatabaseService _databaseService;

    public HomeController(IDatabaseService databaseService)
    {
        _databaseService = databaseService;
    }

    public IActionResult Index()
    {
        var data = _databaseService.GetData();
        return View(data);
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDatabaseService, DatabaseService>();
}
Enter fullscreen mode Exit fullscreen mode

In this example, we first define an IDatabaseService which fetches data from a database. We have a DatabaseService class implementing this interface. We have a HomeController which needs access to the database and so, receives IDatabaseService as a parameter in its constructor.

This HomeController doesn’t care about who provides IDatabaseService or how it is created, all it knows is that it needs IDatabaseService to do its work, making the system more modular and testable.

Now, when HomeController requests IDatabaseService, instead of creating a new DatabaseService every time, the DatabaseService which was freshly created and stored by AddSingleton is supplied.

Singleton Pattern in Game Development: C# Unity

Get ready to dive headfirst into the world of gaming applications with Unity and Singleton! Unity is a powerful game development engine and learning Singleton within its context can unlock some superpowers to create more efficient game mechanics. Are you keen to learn? We’ve got a lot to cover!

Application of Singleton in Unity

In Unity, Singleton might wear a slightly different look, but it still carries the same purpose – to avoid multiple script instances from running. Now, this concept may sound a bit tricky initially, but let’s break it down and understand it better.

public class GameManager : MonoBehaviour
{
    // Static instance stored
    public static GameManager instance;

    // Called when the script instance is being loaded
    void Awake()
    {
        if (instance != null)
        {
            Destroy(gameObject);  // Ensures only one instance is available
        }
        else
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
    }

    // Remaining code of game manager
}
Enter fullscreen mode Exit fullscreen mode

Imagine playing a video game where our character ‘GamePlayer’ needs to interact with the ‘GameManager.’ The ‘GameManager’ is a Unity GameObject that controls different aspects of the game, like keeping track of player’s life, loading different levels, etc. Now it’s a pickle if there are multiple GameManagers to manage a single GamePlayer, right?

That’s where Singleton comes in. It ensures that only one instance of GameManager exists throughout the game scenes. How does it work here? The script first checks if an instance of the GameManager already exists.

If it does, it destroys the new GameObject attempting to create a fresh new GameManager. If it doesn’t, it assigns the instance variable to the new GameManager.

Let’s say we have another GameObject in the game, say ‘Enemy’. Enemy needs information from GameManager to interact with the GamePlayer. How will it access the singleton GameManager?

public class Enemy : MonoBehaviour
{
    //Reference to game manager
    GameManager gameManager;

    void Start()
    {
        gameManager = GameManager.instance;     // Assigns gameManager from Singleton instance
    }

    void Update()
    {
        // Use gameManager to interact with GamePlayer.
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above Enemy class script, it’s seen that we can easily access the GameManager singleton instance. We first create a GameManager reference, and on Start(), we assign it with our Singleton GameManager instance. Now, Enemy can comfortably interact with GamePlayer via this singleton GameManager throughout the game. Isn’t it wonderfully simple once we understand the process?

Leveraging C# Services addSingleton

To round off our Singleton its quest in C#, let’s do a bit more digging into the addSingleton method under the services of C#. Remember the Singleton class we created before? We’re giving it a turbo boost using C# services!

The Power of C# Services addSingleton

The addSingleton method revs up our Singleton’s engine in ASP.NET Core by keeping it alive throughout the application run – exactly what a Singleton aims for.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IEmailService, EmailService>();
}
Enter fullscreen mode Exit fullscreen mode

In this code snippet, we’re ordering ASP.NET Core to maintain a single instance of EmailService throughout the lifespan of the application. Like a one-stop shop, every time IEmailService is requested, EmailService is at your service!

Now, let’s understand this better with a simple example.

Imagine you’re at a fast-food restaurant and ordering a meal. You ask for a burger (that’s your IEmailService). The restaurant (our application) only has one kind of burger – the EmailService. So every time you ask for a burger, you’re served the EmailService burger. Can you ask for a different one? Nope. There’s only one type, and it’s ready to be served as long as the restaurant (application) stays open.

C# Services addSingleton In Action: A Practical Example

Let’s take another real-life scenario: Let’s say we’re developing a blog website. For simplification, we will consider that the blog posts will be written by a single author. Hence, the author’s information remains consistent throughout the application, making AuthorInformation a perfect candidate for a Singleton service.

public class AuthorInformation
{
    public string Name { get; set; }
    public string Email { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

First, we create an AuthorInformation class with a few properties.

Then, we register AuthorInformation as a Singleton service in Startup.cs file.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<AuthorInformation>();
}
Enter fullscreen mode Exit fullscreen mode

Every time somewhere in our application if the AuthorInformation is requested, the ASP.NET Core serves the same instance we asked for.

Summing Up with C# Services addSingleton

In conclusion, the addSingleton method does just what we’d hope from a Singleton design pattern in ASP.NET Core: It gives us a single, persistent instance throughout our application’s runtime.

With that, we’ve explored how deep the Singleton rabbit hole goes in C#. While it seems like a lot to digest, remember – practice makes perfect! Keep coding, and you’ll turn into a C# Singleton maestro sooner than you think! The Singleton journey doesn’t stop here, however. There are many more voyages awaiting you in the vast ocean of C#, each one richer than the last. As always, code wisely, explore freely, and most importantly, enjoy the ride!

Top comments (4)

Collapse
 
jaywilkinson profile image
James Wilkinson

very nicely detailed post

Collapse
 
bytehide profile image
ByteHide

Thank you James!

Collapse
 
karenpayneoregon profile image
Karen Payne

Well written, I can see this article helping beginners to advance developers.

Only thing I'd change is the EmailService burger analogy.

Collapse
 
onlinemsr profile image
Raja MSR

I really appreciated the way you explained everything in a way that was easy to understand.

I prefer Lazy to create singleton.