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;
}
}
}
}
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");
}
}
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();
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
}
}
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
}
}
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.
- A private static instance of the class itself.
- A private constructor.
- 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;
}
}
}
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;
}
}
}
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;
}
}
}
}
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;
}
}
}
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;
}
}
}
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>();
}
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>();
}
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
}
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.
}
}
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>();
}
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; }
}
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>();
}
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)
very nicely detailed post
Thank you James!
Well written, I can see this article helping beginners to advance developers.
Only thing I'd change is the EmailService burger analogy.
I really appreciated the way you explained everything in a way that was easy to understand.
I prefer Lazy to create singleton.