Какие времена жизни объектов вы знаете в IoC-контейнере ASP.NET Core?

Ответ

IoC-контейнер ASP.NET Core управляет временем жизни зарегистрированных сервисов через три основных варианта, которые критически важны для корректной работы приложения, особенно в веб-сценариях:

  1. Transient (Временный)

    • Поведение: Контейнер создаёт новый экземпляр каждый раз, когда сервис запрашивается (каждый вызов GetService или внедрение через конструктор).
    • Использование: Для лёгких, не имеющих состояния (stateless) сервисов, где каждый вызов должен быть изолирован. Например, простые валидаторы, преобразователи данных.
      services.AddTransient<IEmailValidator, EmailValidator>();
  2. Scoped (В рамках области)

    • Поведение: В рамках одной области (scope) используется один и тот же экземпляр. В веб-приложении новая область создаётся автоматически для каждого HTTP-запроса. Это самый часто используемый вариант.
    • Использование: Для сервисов, которые должны сохранять своё состояние в рамках одного запроса, например, DbContext в Entity Framework Core, репозитории, сервисы бизнес-логики.
      services.AddScoped<IOrderRepository, OrderRepository>();

      Важно: Попытка разрешить Scoped-сервис из Singleton или вне области видимости приведёт к исключению.

  3. Singleton (Одиночка)

    • Поведение: Контейнер создаёт один экземпляр при первом запросе и затем возвращает его на протяжении всей жизни приложения.
    • Использование: Для сервисов, которые дорого создавать (кэши, клиенты для внешних API с пулом соединений) или должны быть единым источником состояния для всего приложения. Необходимо проектировать такие сервисы как потокобезопасные (thread-safe).
      services.AddSingleton<ICacheService, DistributedCacheService>();

Практическое правило: Используйте Scoped по умолчанию для сервисов, работающих с данными запроса. Используйте Transient для простых, не имеющих состояния утилит. Используйте Singleton осознанно, только для сервисов, которые действительно должны существовать в единственном экземпляре.

Ответ 18+ 🔞

Ну что, друзья, давайте разберём эту магию, без которой ваш ASP.NET Core приложение просто ляжет и не встанет. Речь про то, как контейнер решает, когда и какого мужика (то есть сервис) вам подсовывать. Три кита, три варианта, и если их перепутать — будет вам хиросима, а не приложение.

Transient (Временный, он же «одноразовый»)
Каждый раз, когда вы просите этот сервис — будь то через конструктор или ручной вызов — контейнер, сука, создаёт новый экземпляр. Представьте, что вы каждый раз берёте новый одноразовый стакан в офисе. Выпил — выкинул.
Когда юзать: Для всякой лёгкой, безголовой хуйни, которая не помнит, что было в прошлый раз. Ну там валидатор email, который просто проверил и забыл.

services.AddTransient<IEmailValidator, EmailValidator>();

Вот этот парень живёт по принципу «использовал и в помойку». Никакой памяти, никакого состояния — чистая функция, блядь.

Scoped (Областной, он же «запросецкий»)
А вот это, друзья, самый частый гость в наших палестинах. В рамках одной области (scope) — один и тот же экземпляр. В веб-приложении новая область — это каждый новый HTTP-запрос, ёпта. Всё, что происходит в рамках одного запроса, делит один экземпляр. Закончился запрос — экземпляр на свалку истории.
Когда юзать: Для всего, что должно помнить себя в рамках одного запроса. Классика — ваш DbContext из Entity Framework. Ну представьте: открыли транзакцию, поработали с базой в рамках одного запроса — всё в одном экземпляре. Закрыли — убили.

services.AddScoped<IOrderRepository, OrderRepository>();

Важный момент, блядь: Если вы попробуете достать Scoped-сервис из Singleton-сервиса или просто из корня приложения — получите исключение, которое пошлёт вас лесом. Потому что у Scoped-сервиса нет области вне запроса, он как рыба без воды — сразу дохнет.

Singleton (Одиночка, он же «вечный жилец»)
Создаётся один раз при первом запросе и живёт, сука, до последнего вздоха приложения. Как тот дед на лавочке, который видел всё.
Когда юзать: Только для тех сервисов, которые реально должны быть в одном экземпляре и которые несут общее состояние или они овердохуища дорогие в создании. Например, кэш в памяти или клиент для внешнего API, который сам управляет соединениями.

services.AddSingleton<ICacheService, DistributedCacheService>();

Но предупреждение, ёб твою мать: Если вы запихнете в Singleton что-то, что не является потокобезопасным, то при параллельных запросах получите такой пиздец, что мало не покажется. Все потоки будут драться за один экземпляр, как голодные псы за кость.

Итоговое правило жизни, чувак:
По умолчанию — Scoped. Это для всего, что связано с данными и логикой запроса.
Для простых утилит без состояния — Transient.
А Singleton — только когда вы точно уверены, что он нужен в одном экземпляре, и вы продумали потокобезопасность. Иначе будет не «одиночка», а «один в поле воин», которого растопчут в первую же секунду.