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

Ответ

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) управляется контейнером.

Ответ 18+ 🔞

А, синглтон, блядь! Ну это ж классика, ёпта, как "Муму" у Тургенева — все знают, а половина нихуя не понимает, зачем оно надо.

Смотри, представь себе, что у тебя есть, ну, например, главный настройщик всего приложения. Конфигурация, блядь. И вот ты в каждой комнате своего кода кричишь: "Эй, а какую базу данных использовать-то?". И если у тебя таких настройщиков будет несколько, как этих Толстых, блядь, — один Лев, другой Алексей, — то они начнут говорить разное. Один скажет "localhost", а другой — "какая-то левая амазонка, нахуй". И всё, пиздец, приложение ебнется.

Вот чтобы этого не было, придумали синглтон — это такой единственный царь-батюшка для своего класса. Создали его один раз, и все ходят к нему на поклон за данными. Больше экземпляров — ни-ни. Как Герасим, только один на всю деревню.

Как это делается по-старинке, с потрохами и блокировками:

public sealed class Singleton // 'sealed' — чтобы наследников, блядь, не было, а то начнут плодиться
{
    // Вот он, наш царь в ожидании. volatile — чтоб потоки его не ебали с кэшами процессора.
    private static volatile Singleton _instance;
    // Объект для блокировки. Без него в многопоточке будет пиздец — создадут сто царей.
    private static readonly object _lockObject = new object();

    // Конструктор приватный! Это чтобы какой-нибудь умник не сделал 'new Singleton()'. Хуй тебе, а не 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() { } // Приватный конструктор — святое!

    // Только когда кто-то попросит Value — тогда он и создастся. Красота!
    public static Singleton Instance => _lazyInstance.Value;
}

Вот и вся магия. Но, предупреждаю, как и всякая глобальная хуйня, синглтон — палка о двух концах. Если ты его начнёшь таскать по всему коду, как ту самую Муму, то в итоге всё так переплетётся, что тестировать будет невозможно. Современные ребята из-за этого вообще против него, говорят — антипаттерн. Вместо этого они используют внедрение зависимостей (DI), где контейнер сам решает, что у тебя будет один экземпляр на все запросы (AddSingleton). Это, конечно, цивилизованнее, но чтобы понять DI, нужно сначала с синглтоном разобраться, а то будешь как Герасим — мычать и ничего не понимать.

Короче, инструмент как инструмент. Главное — не выеби себе им мозг.