Ответ
Это три основных времени жизни (lifetime) сервисов, регистрируемых в DI-контейнере ASP.NET Core. Выбор влияет на производительность, потребление памяти и корректность работы приложения.
1. Transient (AddTransient)
- Поведение: Создаётся новый экземпляр сервиса каждый раз, когда он запрашивается из контейнера.
- Использование: Для лёгких, статусных (stateful) или дорогих в создании сервисов, которые не должны разделяться.
- Пример:
services.AddTransient<IMyService, MyService>(); // В контроллере дважды запрошен IMyService: var service1 = scope.ServiceProvider.GetService<IMyService>(); // Новый экземпляр var service2 = scope.ServiceProvider.GetService<IMyService>(); // Ещё один новый экземпляр
2. Scoped (AddScoped)
- Поведение: Создаётся один экземпляр на область видимости (scope). В веб-приложении область видимости по умолчанию соответствует одному HTTP-запросу.
- Использование: Идеально для сервисов, которым необходим контекст в рамках одной операции (например,
DbContextEntity Framework Core). - Пример:
services.AddScoped<IMyRepository, MyRepository>(); // В рамках одного запроса несколько классов получат один и тот же экземпляр репозитория.
3. Singleton (AddSingleton)
- Поведение: Создаётся один экземпляр на всё время жизни приложения (с момента первого разрешения).
- Использование: Для статусных (stateless), потокобезопасных сервисов, таких как кэш в памяти, логгеры или клиенты для внешних API.
- Пример:
services.AddSingleton<IMemoryCache, MemoryCache>();
Критические правила и best practices:
- Потокобезопасность: Singleton-сервисы должны быть потокобезопасными, так как к ним возможен одновременный доступ из множества потоков.
- Зависимости (Captive Dependency): Никогда не инжектируйте Scoped-сервис в Singleton. Singleton «захватит» Scoped-сервис, и тот будет жить вечно, что может привести к утечке памяти (например,
DbContextне будет освобождать соединения). - Производительность: Transient для тяжёлых объектов может создать нагрузку, а Singleton для объектов с состоянием — ошибки.
Ответ 18+ 🔞
Слушай, давай разберём эту тему про времена жизни сервисов в ASP.NET Core, а то народ часто путается и потом приложение ведёт себя как пьяный ёжик в тумане.
Вот есть три основных варианта, как сервис может жить. Выбираешь неправильно — получаешь либо утечку памяти, либо баги, либо производительность на уровне улитки в смоле.
1. Transient (Временный, AddTransient)
- Как работает: Каждый раз, когда ты просишь этот сервис из контейнера, тебе выдают новый, свежеиспечённый экземпляр. Без раздумий, просто плодят их как кроликов.
- Когда юзать: Для сервисов, которые лёгкие, но с состоянием, или которые создавать — недорого. Либо наоборот, для таких тяжёлых, которые делить между клиентами нельзя — чтобы каждый тащил своё бремя.
- Пример в коде:
services.AddTransient<IMyService, MyService>(); // Два вызова подряд: var service1 = scope.ServiceProvider.GetService<IMyService>(); // Новый! var service2 = scope.ServiceProvider.GetService<IMyService>(); // И снова новый! Это два разных объекта, Карл!
2. Scoped (С областью видимости, AddScoped)
- Как работает: Здесь уже умнее. Один экземпляр создаётся на одну "область" (scope). В веб-приложении по умолчанию одна область — это один HTTP-запрос. Всё, что в рамках одного запроса, — делит один экземпляр. Запрос кончился — экземпляр на свалку.
- Когда юзать: Идеально для вещей, которые завязаны на контекст операции. Классика —
DbContextиз Entity Framework Core. Чтобы все репозитории в одном запросе работали с одной сессией БД и не устраивали бардак. - Пример в коде:
services.AddScoped<IMyRepository, MyRepository>(); // Два разных класса в одном запросе получат ссылку на один и тот же объект репозитория. Как братья-близнецы, только без драк.
3. Singleton (Одиночка, AddSingleton)
- Как работает: Самый простой и опасный. Один экземпляр на всё время работы приложения. Создали при старте — и он живёт, пока приложение не убьют. Все запросы, все потоки — лезут к одной и той же сущности.
- Когда юзать: Только для сервисов без состояния (stateless), которые к тому же потокобезопасны. Типичные кандидаты: кэш в памяти, логгер, какой-нибудь клиент для внешнего API, который умеет работать в многопоточке.
- Пример в коде:
services.AddSingleton<IMemoryCache, MemoryCache>();
А теперь, блядь, главные правила, которые нарушать — себе дороже:
- Потокобезопасность для Singleton — это закон. Если твой синглтон не умеет работать, когда два потока одновременно в него тычутся, — будет пиздец, тихий и неприметный, пока продакшн не ляжет. Проверяй это в первую очередь.
- Никогда не втыкай Scoped-сервис в Singleton! Это грех смертный, называется "Captive Dependency". Синглтон живёт вечно, он схватит скопед-сервис при своём рождении и будет держать его до скончания времён. Если это был
DbContext, то соединение с базой так и не закроется, и память будет течь, как решето. В рот меня чих-пых, не делай так! - С производительностью думай. Transient для объекта, который весит как чугунный мост, — убьёт перфоманс, потому что его будут создавать каждую секунду. Singleton для объекта с состоянием — гарантированная порча данных между пользователями. Думай, что ты делаешь, а не тыкай наугад.
Вот и вся магия. Выбирай с умом, и приложение будет летать, а не ползать, как рак по шерсти.