Ответ
Время жизни сервиса определяет, как долго контейнер Dependency Injection будет хранить и повторно использовать его экземпляр. Правильный выбор критичен для архитектуры приложения.
1. Transient (Мгновенный)
- Создание: Новый экземпляр при каждом запросе сервиса, даже в рамках одного вызова метода.
- Аналогия: Как
new MyService()каждый раз. - Когда использовать:
- Легковесные сервисы.
- Сервисы со внутренним состоянием (stateful), которое не должно разделяться (например, обработчик команды).
- Сервисы, создание которых дёшево.
- Предостережение: Регистрация «тяжёлого» сервиса (например, с сетевыми вызовами в конструкторе) как Transient может убить производительность.
2. Scoped (В рамках области)
- Создание: Один экземпляр в рамках области видимости (scope). В веб-приложении область — это HTTP-запрос.
- Аналогия: Один экземпляр на одну бизнес-транзакцию.
- Когда использовать:
- Классический пример —
DbContextEntity 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. повторное использование), потреблением памяти и корректностью работы с состоянием.