If you’ve been coding in .NET for a while, you’ve likely come across the Singleton pattern. It’s one of the most widely used design patterns in backend development, yet it’s often misunderstood or misused.
In this post, I’ll break down the Singleton pattern in simple terms, show you real-world use cases, and provide a hands-on .NET example that you can apply in your projects today.
What is the Singleton Pattern?
The Singleton pattern is a creational design pattern that ensures only one instance of a class is created and provides a global access point to that instance.
Think of it like a CEO of a company. There’s only one CEO, and everyone references the same CEO instance. In software, we use Singleton when we need a single, shared object across the application.
When Should You Use Singleton?
The Singleton pattern is useful when:
✅ You need a single, shared instance to manage global state (e.g., logging, caching, configuration settings).
✅ Creating multiple instances would be expensive (e.g., database connections, file system access).
✅ You need to coordinate actions across multiple parts of your application (e.g., thread pool, authentication token management).
🚫 However, avoid Singleton when:
❌ You need different instances for different users (e.g., per-request data in a web API).
❌ It makes unit testing harder due to shared state (e.g., global state can lead to hidden dependencies).
Implementing Singleton in C# (Thread-Safe Way)
Let’s implement a thread-safe Singleton in .NET
using System;
public sealed class Singleton
{
private static readonly Lazy<Singleton> instance = new(() => new Singleton());
// Private constructor prevents direct instantiation
private Singleton()
{
Console.WriteLine("Singleton instance created.");
}
// Public property to access the instance
public static Singleton Instance => instance.Value;
public void ShowMessage()
{
Console.WriteLine("Hello from Singleton!");
}
}
class Program
{
static void Main()
{
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
s1.ShowMessage();
// Confirm both variables reference the same instance
Console.WriteLine(ReferenceEquals(s1, s2)); // Output: True
}
}
Explanation:
🔹 Lazy<T>
ensures thread safety and lazy initialization, so the object is created only when needed.
🔹 sealed
prevents other classes from inheriting and breaking the pattern.
🔹 private constructor
ensures no external instantiation.
🔹 Instance
property provides a single global access point.
Real-World Use Cases of Singleton in .NET
1️⃣ Logging Service
A Singleton is perfect for a logging system, ensuring all logs go through a single instance.
public sealed class Logger
{
private static readonly Lazy<Logger> instance = new(() => new Logger());
public static Logger Instance => instance.Value;
private Logger() { }
public void Log(string message) => Console.WriteLine($"Log: {message}");
}
// Usage
Logger.Instance.Log("Application started...");
✅ Avoids creating multiple log file handlers.
2️⃣ Configuration Manager
Store application-wide settings in a Singleton to ensure consistency.
public sealed class ConfigManager
{
private static readonly Lazy<ConfigManager> instance = new(() => new ConfigManager());
public static ConfigManager Instance => instance.Value;
public string ConnectionString { get; private set; }
private ConfigManager() => ConnectionString = "Server=MyDB;Database=App;";
}
// Usage
string connString = ConfigManager.Instance.ConnectionString;
✅ Ensures all components use the same configuration.
3️⃣ Caching Data
Use Singleton for caching frequently accessed data to improve performance.
public sealed class CacheManager
{
private static readonly Lazy<CacheManager> instance = new(() => new CacheManager());
public static CacheManager Instance => instance.Value;
private readonly Dictionary<string, object> cache = new();
private CacheManager() { }
public void Set(string key, object value) => cache[key] = value;
public object Get(string key) => cache.ContainsKey(key) ? cache[key] : null;
}
// Usage
CacheManager.Instance.Set("user1", "John Doe");
var user = CacheManager.Instance.Get("user1");
✅ Reduces redundant database calls.
Singleton in ASP.NET Core with Dependency Injection (Best Practice)
Although Singleton can be implemented manually, ASP.NET Core’s Dependency Injection (DI) system provides a better way:
Registering a Singleton in ASP.NET Core
services.AddSingleton<IMyService, MyService>();
Example of a Singleton Service
public interface IMyService
{
void DoWork();
}
public class MyService : IMyService
{
public void DoWork() => Console.WriteLine("MyService is working.");
}
// Usage in a Controller
public class HomeController : Controller
{
private readonly IMyService _myService;
public HomeController(IMyService myService)
{
_myService = myService;
}
public IActionResult Index()
{
_myService.DoWork();
return View();
}
}
✅ The DI approach makes the Singleton testable and avoids static references.
Conclusion
The Singleton pattern is a powerful tool in .NET, but it should be used wisely. It’s perfect for managing shared resources like logging, configuration, caching, and database connections. However, overusing it can make your code harder to test and maintain.
👉 Best practice? Use ASP.NET Core’s DI system to register Singletons instead of manually implementing them.
Now that you understand Singleton, how do you use it in your projects? Share your thoughts in the comments!
Leave a Reply