Что такое паттерн Singleton (Одиночка)?

«Что такое паттерн Singleton (Одиночка)?» — вопрос из категории Паттерны, который задают на 28% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Singleton — это порождающий паттерн проектирования, который гарантирует, что у класса существует только один экземпляр, и предоставляет к нему глобальную точку доступа.

Зачем это нужно? Для управления общим ресурсом, доступ к которому должен быть координирован из одной точки: кэш, конфигурация, логгер, пул соединений с базой данных.

Классическая потокобезопасная реализация на C# (с двойной проверкой):

public sealed class Singleton
{
    // Единственный экземпляр. 'volatile' гарантирует корректную работу в многопоточной среде.
    private static volatile Singleton _instance;
    private static readonly object _lockObject = new object();

    // Приватный конструктор блокирует создание экземпляров через 'new'.
    private Singleton() { }

    // Публичное статическое свойство для доступа.
    public static Singleton Instance
    {
        get
        {
            // Первая проверка (для быстродействия, если экземпляр уже создан).
            if (_instance == null)
            {
                lock (_lockObject) // Блокировка для одного потока.
                {
                    // Вторая проверка внутри блокировки.
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }
            return _instance;
        }
    }
}

Использование:

var config = Singleton.Instance;

Современный и предпочтительный способ в .NET — использование Lazy<T>:

public sealed class Singleton
{
    // Lazy<T> обеспечивает ленивую инициализацию и потокобезопасность по умолчанию.
    private static readonly Lazy<Singleton> _lazyInstance =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton() { }

    public static Singleton Instance => _lazyInstance.Value;
}

Важно: Паттерн может считаться антипаттерном, если приводит к излишнему глобальному состоянию и усложняет тестирование. В таких случаях лучше использовать внедрение зависимостей (DI), где жизненный цикл объекта (например, AddSingleton) управляется контейнером.