Что такое AddSingleton в ASP.NET Core DI?

Ответ

AddSingleton — это метод расширения для регистрации сервиса с одиночным (Singleton) временем жизни в контейнере внедрения зависимостей (DI) ASP.NET Core.

Поведение: Создается ровно один экземпляр сервиса на все время работы приложения. Этот экземпляр используется для всех запросов и всех пользователей.

Регистрация сервиса:

public void ConfigureServices(IServiceCollection services)
{
    // Регистрация реализации как синглтона
    services.AddSingleton<IMyService, MyService>();

    // Регистрация существующего экземпляра как синглтона
    var instance = new MyService();
    services.AddSingleton<IMyService>(instance);

    // Регистрация типа, который будет использоваться и как сервис, и как реализация
    services.AddSingleton<MyService>();
}

Когда использовать AddSingleton:

  • Сервисы без состояния (Stateless): Кэши (IMemoryCache), клиенты для внешних API (если потокобезопасны), логгеры (фабрика логгеров сама управляет жизненным циклом).
  • Дорогие в создании объекты: Объекты, инициализация которых требует больших ресурсов (например, загрузка конфигурации, установка соединения).
  • Хранилища данных в памяти, которые должны быть доступны всем пользователям (например, in-memory репозиторий для демо).

Критически важные предостережения:

  1. Потокобезопасность (Thread Safety): Поскольку один экземпляр используется одновременно многими запросами, ваша реализация синглтона должна быть потокобезопасной. Используйте конструкции lock, SemaphoreSlim или concurrent-коллекции (ConcurrentDictionary).
  2. Зависимости: Синглтон не должен зависеть от сервисов с временем жизни Scoped (например, DbContext), так как это может привести к утечке памяти или неправильному использованию ресурсов (Scoped-сервис превратится в синглтон).
  3. Disposable: Если синглтон реализует IDisposable, контейнер НЕ вызовет Dispose() автоматически. Утилизацию нужно управлять вручную, например, через IApplicationLifetime.ApplicationStopping.

Ответ 18+ 🔞

А, ну это про AddSingleton! Давай разберём, что это за зверь такой, а то народ часто путается, а потом сидят и чешут репу.

Вот представь себе, что у тебя есть какой-нибудь здоровенный, навороченный кофейный аппарат на кухне в офисе. Он один на всех, стоит себе в углу, гремит. Кто хочет кофе — идёт и льёт из него. Неважно, Вася из бухгалтерии пришёл или Катя из отдела кадров — аппарат один и тот же. Вот AddSingleton — это ровно про это.

Как оно работает, если по-простому: Ты регистрируешь сервис как синглтон — и контейнер DI создаёт его один раз, на старте приложения, и больше не отсоебляется. Этот единственный экземпляр потом тычут всем подряд, кто его запрашивает. Все запросы, все пользователи — все пьют из одной чашки, прости господи.

Как это в коде выглядит:

public void ConfigureServices(IServiceCollection services)
{
    // Самый частый вариант — сказали тип интерфейса и тип реализации.
    // Создаст один экземпляр MyService и будет всем его подсовывать.
    services.AddSingleton<IMyService, MyService>();

    // Вариант для крутых — если у тебя уже есть готовый, настроенный объект.
    // Держи, говорит, вот этот инстанс, и больше никого не трогай.
    var preBuiltInstance = new MyService();
    services.AddSingleton<IMyService>(preBuiltInstance);

    // Ну или если твой сервис — он сам себе и интерфейс, и реализация.
    // Регистрируешь просто тип.
    services.AddSingleton<MyService>();
}

А когда это вообще нужно?

  • Объекты, которые создавать — овердохуища ресурсов. Ну, например, тяжёлый кэш в памяти (IMemoryCache), какой-нибудь сконфигурированный HTTP-клиент для внешнего API, или парсер конфигов, который пол-гигабайта JSON'ов жуёт при старте. Создали один раз — и поехали.
  • Сервисы без состояния (stateless). Им похуй, кто и когда их использует, им не нужно помнить контекст запроса. Главное — чтобы они были потокобезопасными, об этом ниже.
  • Какие-нибудь глобальные хранилища данных в памяти для демо-приложений. Типа in-memory репозитория, где список пользователей лежит в обычном List. Все видят один и тот же список.

А теперь, внимание, самое важное — подводные ебеня!

  1. Потокобезопасность — это твоя головная боль. Поскольку один объект будут дёргать одновременно кучу потоков (каждый запрос — свой поток), ты должен сделать так, чтобы они друг другу не наступили на мозги. Если внутри синглтона есть общее состояние (тот же List), обязательно используй lock, SemaphoreSlim или thread-safe коллекции вроде ConcurrentDictionary. Иначе будет весело: данные поплывут, исключения посыплются, а ты будешь искать, кто тут мудак. А мудак — это ты, потому что не предусмотрел.

  2. Зависимости — смотри в оба. Если твой синглтон вдруг возьмёт и захочет зависеть от сервиса с временем жизни Scoped (например, от DbContext), то жди беды. Scoped-сервис создаётся на время запроса, а потом должен умирать. Но если синглтон за него ухватится, то он не умрёт, а превратится в такого же синглтона-зомби. Это прямая дорога к утечке памяти и к тому, что данные между пользователями начнут смешиваться. Пипец, короче.

  3. Если твой синглтон реализует IDisposable — контейнер его НЕ убьёт. Да-да, он создаст его один раз при старте, а когда приложение будет завершаться — просто оставит его догнивать. Если нужно корректно освободить ресурсы (закрыть файл, сетевое соединение), тебе нужно подписаться на событие завершения приложения (IApplicationLifetime.ApplicationStopping) и там вручную вызвать Dispose(). Иначе получишь тихую, но противную утечку.

Итог: AddSingleton — мощный инструмент, но как молоток. Можно гвоздь забить, а можно себе по пальцам въебать. Используй для действительно глобальных, недешёвых и потокобезопасных вещей. А если сомневаешься — лучше начни со Scoped, меньше шансов что-то запороть.