Implement a thread-safe lazy singleton and explain Lazy<T>
cs-sen-005
Your answer
Answer as you would in a real interview — explain your thinking, not just the conclusion.
Model answer
In modern C# the idiomatic answer is Lazy<T> with LazyThreadSafetyMode.ExecutionAndPublication — the runtime handles all synchronisation. Before Lazy<T> (or when needing explanation), the double-checked locking pattern uses volatile plus a lock to avoid the race between the null check and the constructor. The static initialiser approach is also thread-safe by CLR specification but doesn't support lazy initialisation if Instance is never accessed. For dependency-injected services, a singleton should be registered with AddSingleton rather than the Singleton pattern — the DI container is the single-instance guarantee.
Code example
// Option 1 — Lazy<T> (preferred)
public sealed class ConfigService
{
private static readonly Lazy<ConfigService> _instance =
new(() => new ConfigService(), LazyThreadSafetyMode.ExecutionAndPublication);
public static ConfigService Instance => _instance.Value;
private ConfigService() { /* load config */ }
}
// Option 2 — Double-checked locking (for illustration)
public sealed class LegacySingleton
{
private static volatile LegacySingleton? _instance;
private static readonly object _lock = new();
public static LegacySingleton Instance
{
get
{
if (_instance is null)
lock (_lock)
_instance ??= new LegacySingleton();
return _instance;
}
}
private LegacySingleton() { }
}
// Option 3 — Static holder (CLR-guaranteed, eager)
public sealed class EagerSingleton
{
public static readonly EagerSingleton Instance = new();
private EagerSingleton() { }
}
Follow-up
What does LazyThreadSafetyMode.PublicationOnly mean, and when would you choose it over ExecutionAndPublication?