Ответ
В 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 — только для сервисов с общим состоянием, доступ к которому должен быть синхронизирован.
Ответ 18+ 🔞
А, слушай, про эту вашу инъекцию зависимостей в ASP.NET Core! Ну, там же три главных типа, как они живут, да? Это ж надо понимать, а то потом такие косяки будут, что охуеешь.
Вот смотри, есть три штуки:
Transient — это как одноразовый стаканчик. Каждый раз, когда кто-то просит этот сервис, контейнер тебе новый, свеженький делает. Зарегистрировать — AddTransient. Юзать это для всякой легкой хуйни, которая не должна состояние хранить. Ну, типа Random там, или локализатор какой. Сделал дело — выкинул, похуй.
Scoped — вот это уже поинтереснее. Создается один раз на область видимости. В веб-приложении — один раз на HTTP-запрос. Все в рамках одного запроса юзают один и тот же экземпляр. Регистрация — AddScoped. Это для всего, что завязано на контекст запроса: твой DbContext из Entity Framework, репозитории, сервисы бизнес-логики. Удобно, логично.
Singleton — это уже монументальная хуйня. Создается один раз на всё время работы приложения и живёт, пока приложение не сдохнет. AddSingleton. Это для сервисов с общим состоянием или которые овердохуища ресурсов жрут при создании: кэши (IMemoryCache), клиенты (через фабрику), конфигурация какая-нибудь глобальная.
А теперь, внимание, ебушки-воробушки, главные грабли, на которые все наступают:
-
Singleton НИ В КОЕМ СЛУЧАЕ не должен зависеть от Scoped или Transient сервисов. Вообще, блядь, никогда! Это называется captive dependency — зависимость в плену. Представь: у тебя Singleton-сервис, как царь и бог, живёт вечно. А ты в него засунул Scoped-сервис, который по задумке должен умирать после каждого запроса. И что? А него нихуя не умрёт! Он будет висеть в памяти, как проклятый. Особенно весело с
DbContext— он кэшировать данные начнёт, соединения не будет отпускать, и в итоге у тебя приложение ебнет по памяти. Пиздец и разбор полётов.// НЕ, НЕ, НЕ! СОВСЕМ НЕ ТАК! Singleton, зависящий от Scoped — это билет в ад. public class BadCacheService { private readonly AppDbContext _context; // Scoped, Карл! public BadCacheService(AppDbContext context) => _context = context; // Вот тут и начинается пиздец. } -
А если надо Scoped сервис юзать вне HTTP-запроса? Ну, в фоновой задаче, в каком-нибудь
IHostedService. Там же запроса нет, scope-а нет. И что делать? А вот так: руками создавать область видимости через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 — это твой дефолтный, рабочий вариант для 90% сервисов, где есть какая-то логика, связанная с запросом или транзакцией. Начинай думать с него.
- Transient — для простых, безголовых утилиток, которые не хранят состояния между вызовами. Сделал — выбросил.
- Singleton — только для реально глобальных, общих штук, доступ к которым нужно как-то синхронизировать. Или которые так дороги в создании, что проще один раз создать и забыть. Но без фанатизма, а то опять captive dependency получишь, и волосы на жопе вылезут.