Ответ
В ASP.NET Core Dependency Injection (DI) контейнер управляет временем жизни сервисов. Существует три основных типа (Lifetime), которые определяют, как и когда создается экземпляр сервиса:
| Тип | Время жизни | Регистрация | Типичное использование |
|---|---|---|---|
| Transient | Создается каждый раз при запросе из контейнера. | AddTransient<TService, TImplementation>() |
Легковесные, статусные сервисы (например, Random, IStringLocalizer). Не должны хранить состояние между вызовами. |
| Scoped | Создается один раз на область видимости (scope). Для веб-приложения — один на HTTP-запрос. | AddScoped<TService, TImplementation>() |
Сервисы, работающие в контексте одного запроса: DbContext (Entity Framework Core), репозитории, сервисы бизнес-логики. |
| Singleton | Создается один раз на все время работы приложения. | AddSingleton<TService, TImplementation>() |
Сервисы с общим состоянием или дорогие в создании: кэши (IMemoryCache), клиенты (HttpClient с IHttpClientFactory), конфигурация. |
Критические моменты и best practices:
-
Зависимости между сервисами с разным временем жизни:
- Singleton НИКОГДА не должен зависеть от Scoped или Transient сервисов. Это приведет к captive dependency — Scoped-сервис, захваченный Singleton-ом, будет жить вечно, что может вызвать утечку памяти (например,
DbContextбудет кэшировать данные и не освобождать соединения).// НЕПРАВИЛЬНО: Singleton зависит от Scoped. public class BadCacheService { private readonly AppDbContext _context; // Scoped! public BadCacheService(AppDbContext context) => _context = context; // Ошибка! }
- Singleton НИКОГДА не должен зависеть от Scoped или Transient сервисов. Это приведет к captive dependency — Scoped-сервис, захваченный Singleton-ом, будет жить вечно, что может вызвать утечку памяти (например,
-
Работа с Scoped сервисами вне HTTP-запроса (в фоновых задачах, через
IHostedService):-
Необходимо вручную создать область видимости с помощью
IServiceScopeFactory.public class BackgroundWorker : IHostedService { private readonly IServiceScopeFactory _scopeFactory; public BackgroundWorker(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory; public async Task DoWorkAsync() { // Создаем новую область видимости using (var scope = _scopeFactory.CreateScope()) { // Получаем Scoped-сервис из новой области var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); await scopedService.ProcessDataAsync(); } // Здесь scoped-сервисы будут корректно удалены. } }
-
Как выбрать? Всегда начинайте с Scoped для сервисов с логикой запроса. Используйте Transient для простых, не имеющих состояния утилит. Singleton — только для сервисов с общим состоянием, доступ к которому должен быть синхронизирован.