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.
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!");
}
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>();
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!
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."