Singleton Pattern
Intent
Ensure a class has only one instance and provide a global point of access to it.
Problem
You need exactly one instance of a class to:
- Coordinate actions across the system (logging, configuration)
- Manage shared resources (connection pools, caches)
- Provide centralized control (settings, state)
Solution
Modern C# - Lazy (Recommended)
public sealed class DatabaseConnection
{
private static readonly Lazy<DatabaseConnection> _instance =
new Lazy<DatabaseConnection>(() => new DatabaseConnection());
public static DatabaseConnection Instance => _instance.Value;
private readonly string _connectionString;
private DatabaseConnection()
{
Console.WriteLine("Initializing database connection...");
_connectionString = "Server=localhost;Database=myapp;";
// Expensive initialization only happens once
}
public void Query(string sql)
{
Console.WriteLine($"Executing: {sql}");
}
}
// Usage
var db = DatabaseConnection.Instance;
db.Query("SELECT * FROM Users");
var db2 = DatabaseConnection.Instance;
Console.WriteLine(db == db2); // True - same instance
Static Constructor (Also Thread-Safe)
public sealed class Logger
{
private static readonly Logger _instance = new Logger();
// Static constructor guarantees single initialization
static Logger() { }
public static Logger Instance => _instance;
private Logger()
{
Console.WriteLine("Logger initialized");
}
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}");
}
}
Generic Singleton Base
public abstract class Singleton<T> where T : class
{
private static readonly Lazy<T> _instance =
new Lazy<T>(() => CreateInstance());
public static T Instance => _instance.Value;
private static T CreateInstance()
{
// Find non-public constructor
var constructor = typeof(T).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, Type.EmptyTypes, null);
if (constructor == null)
throw new InvalidOperationException(
$"Type {typeof(T)} must have a private parameterless constructor");
return (T)constructor.Invoke(null);
}
}
// Usage
public sealed class AppConfig : Singleton<AppConfig>
{
public string AppName { get; } = "MyApplication";
public string Version { get; } = "1.0.0";
private AppConfig() { } // Private constructor required
}
var config = AppConfig.Instance;
Console.WriteLine(config.AppName);
Anti-Patterns to Avoid
Double-Checked Locking (Outdated)
// AVOID: Complex and error-prone
public sealed class OldStyleSingleton
{
private static OldStyleSingleton _instance;
private static readonly object _lock = new object();
public static OldStyleSingleton Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new OldStyleSingleton();
}
}
}
return _instance;
}
}
}
// BETTER: Use Lazy<T> instead
Non-Thread-Safe (Bug)
// BAD: Race condition possible
public sealed class BrokenSingleton
{
private static BrokenSingleton _instance;
public static BrokenSingleton Instance
{
get
{
// Two threads could both create instances!
if (_instance == null)
{
_instance = new BrokenSingleton();
}
return _instance;
}
}
}
Singleton vs Static Class
| Aspect | Singleton | Static Class |
|---|---|---|
| Inheritance | Can implement interfaces | Cannot |
| Instance control | Lazy initialization | Always available |
| Testing | Can be mocked (with interface) | Hard to mock |
| State | Instance state | Static state only |
| Dependency injection | Supported | Not directly |
Testable Singleton
// Interface for testability
public interface IApplicationSettings
{
string GetSetting(string key);
void SetSetting(string key, string value);
}
// Singleton implementation
public sealed class ApplicationSettings : IApplicationSettings
{
private static readonly Lazy<ApplicationSettings> _instance =
new Lazy<ApplicationSettings>(() => new ApplicationSettings());
public static ApplicationSettings Instance => _instance.Value;
private readonly Dictionary<string, string> _settings = new();
private ApplicationSettings()
{
// Load settings from file/database
_settings["Environment"] = "Production";
}
public string GetSetting(string key) =>
_settings.TryGetValue(key, out var value) ? value : null;
public void SetSetting(string key, string value) =>
_settings[key] = value;
}
// In production
services.AddSingleton<IApplicationSettings>(ApplicationSettings.Instance);
// In tests
services.AddSingleton<IApplicationSettings>(new MockSettings());
Singleton with Dependency Injection
Modern preference: Let the DI container manage singleton lifetime.
// Service that should be singleton
public class CacheService : ICacheService
{
private readonly ConcurrentDictionary<string, object> _cache = new();
public void Set(string key, object value) => _cache[key] = value;
public T Get<T>(string key) => _cache.TryGetValue(key, out var v) ? (T)v : default;
}
// Register as singleton in DI
services.AddSingleton<ICacheService, CacheService>();
// Framework ensures single instance
public class UserService
{
private readonly ICacheService _cache;
public UserService(ICacheService cache) // Same instance every time
{
_cache = cache;
}
}
When to Use Singleton
Good Use Cases:
- Configuration - Read-only settings
- Logging - Centralized log management
- Caching - Shared cache instance
- Connection pooling - Database connection management
Avoid Singleton When:
- State changes frequently
- Testing requires different instances
- Object has heavy dependencies
- Simple static methods would suffice
Real-World Examples
// HttpClient - Microsoft recommends singleton usage
public class ApiClientService
{
private static readonly HttpClient _httpClient = new HttpClient
{
BaseAddress = new Uri("https://api.example.com"),
Timeout = TimeSpan.FromSeconds(30)
};
public async Task<string> GetAsync(string path)
{
return await _httpClient.GetStringAsync(path);
}
}
// Configuration
public sealed class AppConfiguration
{
private static readonly Lazy<AppConfiguration> _instance =
new Lazy<AppConfiguration>(() => new AppConfiguration());
public static AppConfiguration Instance => _instance.Value;
public string DatabaseConnection { get; }
public string ApiKey { get; }
public bool IsProduction { get; }
private AppConfiguration()
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
DatabaseConnection = config["ConnectionStrings:Default"];
ApiKey = config["ApiKey"];
IsProduction = config["Environment"] == "Production";
}
}
Interview Tips
Common Questions:
- βHow do you implement thread-safe singleton?β
- βWhat are the drawbacks of singleton?β
- βWhen would you use singleton vs static class?β
Key Points:
- Use
Lazy<T>for thread-safe lazy initialization - Prefer DI container for singleton management in modern apps
- Singletons can make testing difficult
- Hidden dependencies (global state) are a code smell
- .NET
HttpClientis a good example of recommended singleton usage
Red Flags:
- Singletons everywhere (overuse)
- Using singleton to avoid proper DI
- Mutable singleton state without synchronization
- Static classes disguised as singletons