В чём разница между жизненными циклами зависимостей (Transient, Scoped, Singleton) в ASP.NET Core DI?

Ответ

Время жизни сервиса определяет, как долго контейнер Dependency Injection будет хранить и повторно использовать его экземпляр. Правильный выбор критичен для архитектуры приложения.

1. Transient (Мгновенный)

  • Создание: Новый экземпляр при каждом запросе сервиса, даже в рамках одного вызова метода.
  • Аналогия: Как new MyService() каждый раз.
  • Когда использовать:
    • Легковесные сервисы.
    • Сервисы со внутренним состоянием (stateful), которое не должно разделяться (например, обработчик команды).
    • Сервисы, создание которых дёшево.
  • Предостережение: Регистрация «тяжёлого» сервиса (например, с сетевыми вызовами в конструкторе) как Transient может убить производительность.

2. Scoped (В рамках области)

  • Создание: Один экземпляр в рамках области видимости (scope). В веб-приложении область — это HTTP-запрос.
  • Аналогия: Один экземпляр на одну бизнес-транзакцию.
  • Когда использовать:
    • Классический пример — DbContext Entity Framework Core. Это гарантирует, что все репозитории в рамках одного запроса работают с одним контекстом, отслеживают одни сущности и фиксируют изменения атомарно.
    • Сервисы, требующие контекста запроса (например, информация о текущем пользователе).
  • Как создать область вручную (например, в фоновой задаче):
    using (var scope = serviceProvider.CreateScope())
    {
        var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
        // Работаем с scopedService
    } // Здесь scopedService будет уничтожен, если реализует IDisposable

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

  • Создание: Один экземпляр на всё время жизни приложения (точнее, корневого контейнера).
  • Когда использовать:
    • Статусные (stateless), потокобезопасные сервисы.
    • Сервисы-кэши (IMemoryCache).
    • Клиенты для внешних API (например, HttpClient, зарегистрированный правильно через IHttpClientFactory).
    • Конфигурация приложения.
  • Критически важное правило:
    • Не инжектируйте Scoped-сервис в Singleton! Это антипаттерн Captive Dependency. Singleton, созданный один раз, «захватит» Scoped-сервис, и тот никогда не будет освобождён, что ведёт к утечкам памяти (например, соединения с БД не закрываются). Контейнер в ASP.NET Core выбрасывает исключение при такой попытке.

Вывод: Выбор времени жизни — это компромисс между производительностью (создание vs. повторное использование), потреблением памяти и корректностью работы с состоянием.

Ответ 18+ 🔞

А, слушай, про эти lifetime'ы в DI контейнере — это вообще отдельная песня, блядь. Типа, как долго твой сервис будет болтаться в памяти, прежде чем его выкинут на мороз. Выбрал не тот — и привет, утечка памяти или производительность в пизду.

1. Transient (Мгновенный, одноразовый)

  • Что делает: Каждый раз, когда ты просишь этот сервис, тебе выдают новый, свежеиспечённый экземпляр, даже если ты в одном методе десять раз его запросил. Прям как new MyService() на каждый чих.
  • Куда тыкать:
    • Всякую лёгкую хуйню, которую не жалко создавать и убивать.
    • Сервисы с внутренним состоянием, которые друг другу мешать не должны — типа обработчика конкретной команды.
    • Вообще всё, что создаётся быстро и дёшево.
  • Где наебёшься: Если воткнёшь сюда какого-нибудь монстра, который в конструкторе пол-интернета качает, то приложение просто сдохнет под нагрузкой, создавая эту хуйню тысячу раз в секунду.

2. Scoped (В рамках области, "на один запрос")

  • Что делает: Один экземпляр на одну "область". В веб-приложении это обычно один HTTP-запрос. Всё, что в рамках одного запроса, делит один инстанс.
  • Куда тыкать:
    • Классика жанра — DbContext из EF Core. Чтобы все твои репозитории в одном запросе работали с одной сессией, трекали одни и те же ентити и коммитились разом. Иначе будет пиздец и рассинхрон.
    • Сервисы, которым нужен контекст запроса — типа, кто текущий пользователь, ёпта.
  • Как создать scope, если ты не в запросе (например, в фоновой задаче):
    using (var scope = serviceProvider.CreateScope())
    {
        var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
        // Делаешь тут свои дела
    } // А тут scopedService, если он IDisposable, отправится на свалку истории

3. Singleton (Одиночка, навсегда)

  • Что делает: Один экземпляр на всё время работы приложения. Создали один раз при старте — и он живёт, пока приложение не вырубят.
  • Куда тыкать:
    • Безстатусные (stateless) и потокобезопасные сервисы. Которые как швейцарские часы — работают чётко и ничего лишнего не хранят.
    • Кэши (IMemoryCache), потому что они по определению общие.
    • Клиенты для внешних API (правильно зарегистрированные, через IHttpClientFactory, конечно).
    • Конфигурацию приложения, которую читают при старте.
  • Главное правило, которое нарушать — себя не уважать:
    • БЛЯДЬ, НЕ ВСТАВЛЯЙ Scoped-сервис в Singleton! Это называется Captive Dependency — пленный зависимость, блядь. Singleton, созданный один раз, намертво схватит этого Scoped-сервиса, и тот будет висеть вечно, как призрак. Коннекты к БД не закроются, память потечёт, и в ASP.NET Core контейнер тебе вообще исключение кинет, такой он заботливый.

Итог, ёпта: Выбираешь время жизни — выбираешь между скоростью, памятью и тем, чтобы всё не разъехалось по состояниям. Думай головой, а не тыкай наугад, а то будет тебе хиросима в продакшене.