Tutorials Design Patterns Mastery
Singleton Pattern: Thread-safety & Captive Dependencies
On this page
Singleton Pattern
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it. While it sounds simple, implementing a truly professional, thread-safe Singleton in .NET requires understanding Lazy Initialization and Double-Check Locking.
1. The Professional C# Implementation
We use the Lazy<T> type. It is natively thread-safe and ensures the object is only created the exact moment someone actually tries to use it.
public sealed class CacheManager
{
// The 'Lazy' wrapper handles all lock-checking and thread-safety for us!
private static readonly Lazy<CacheManager> _instance =
new Lazy<CacheManager>(() => new CacheManager());
public static CacheManager Instance => _instance.Value;
// Private constructor prevents 'new CacheManager()' from outside
private CacheManager() { }
public void Set(string key, string val) => Console.WriteLine("Cached!");
}
2. Singletons in Dependency Injection
In modern .NET, you don't write "Secret Private Instance" code. You register the class as a Singleton in Program.cs. The DI container handles the "Exactly One" rule for you.
builder.Services.AddSingleton<ICacheService, CacheService>();
3. The Singleton Danger: Captive Dependencies
If your Singleton service (e.g., a Background Worker) tries to inject a Scoped service (e.g., a DbContext), your app will crash or leak memory. Because the Singleton never dies, the Scoped service is "Captured" and never disposed of. This leads to database connections remaining open forever!
4. Interview Mastery
Q: "Is the Singleton pattern an Anti-Pattern?"
Architect Answer: "It depends on *how* it is implemented. The 'Classic' static singleton is often considered an anti-pattern because it introduces global state that is impossible to unit test (you can't mock it). However, using a **DI-managed Singleton** is the industry standard. It gives you all the benefits of a single instance while allowing you to inject interfaces and easily swap implementations during testing."