- PVSM.RU - https://www.pvsm.ru -
В этой статье я постарался собрать краткий гайд по Singleton, Transient и Scoped. Статья рассчитана на тех, кто хотя бы немного знаком с DI в .NET и уже умеет добавлять/запрашивать сервисы через startup/controller или иным образом.
Стандартный DI контейнер в .NET, представленный интерфейсом IServiceCollection имеет 3 способа регистрации зависимостей: Singleton, Transient и Scoped:
services.AddSingleton<ISingletonService, SingletonService>();
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
Способ регистрации влияет на время жизни, момент создания, момент уничтожения, вызов Dispose, а так же накладывает некоторые ограничения при внедрении зависимостей между сервисами с разными жизненными циклами.
Singleton - создает один единственный экземпляр зависимости, который передается при каждой инъекции. Это аналогично созданию одного new SingletonService() и сохранению его в глобальную переменную. Может быть полезно для производительности, если нет необходимости каждый раз создавать новый экземпляр.
// singletonService1 и singletonService2 - это ссылки на ОДИН и тот же объект
var singletonService1 = serviceProvider.GetService<ISingletonService>();
var singletonService2 = serviceProvider.GetService<ISingletonService>();
Transient - создает новый экземпляр зависимости при каждой инъекции. То есть, при каждом запросе ITransientService, мы будем получать только что созданный new TransientService()
// transientService1 и transientService2 - это ссылки на РАЗНЫЕ объекты
var transientService1 = serviceProvider.GetService<ITransientService>();
var transientService2 = serviceProvider.GetService<ITransientService>();
Scoped - это что-то среднее между предыдущими двумя вариантами. Глобально - мы будем многократно создавать новый scope, но внутри каждого scope будет создан только 1 ScopedService.
// scopedService1 и scopedService2 - это ссылки на ОДИН и тот же объект, созданный внутри scope1
var scope1 = serviceProvider.CreateScope();
var scopedService1 = scope1.ServiceProvider.GetService<IScopedService>();
var scopedService2 = scope1.ServiceProvider.GetService<IScopedService>();
// НО scopedService3 и scopedService4 - это ссылки на другой объект, созданный внутри scope2
var scope2 = serviceProvider.CreateScope();
var scopedService3 = scope2.ServiceProvider.GetService<IScopedService>();
var scopedService4 = scope2.ServiceProvider.GetService<IScopedService>();
Это полезно, когда нужно выделить изолированные рабочие сессии. В ASP.NET Core с использованием контроллеров, при каждом http-запросе автоматически создается новый scope:
public class MyController : Controller
{
// контроллеры, пришедшие из MVC, регистрируются как Scoped
// при каждом http запросе они создаются в новом scope вместе с новыми экземплярами scoped зависимостей
public MyController(IScopedService scopedService)
{
}
}
Все Scoped, а так же Transient сервисы со scoped зависимостями должны создаваться ТОЛЬКО внутри конкретного scope, иначе будет выброшено исключение:
// 🚫 если ISomeService является scoped или transient со scoped-зависимостю внутри - упадет ошибка
var someService = serviceProvider.GetService<ISomeService>();
// ✅ правильный вариант, если ISomeService является scoped или transient со scoped-зависимостю внутри
var scope = serviceProvider.CreateScope();
var someService = scope.ServiceProvider.GetService<ISomeService>();
Внутрь Singleton нельзя инжектить Scoped и Transient сервисы. В конструктор Singleton сервиса можно передавать только другие синглтоны, иначе будет выброшено исключение:
public class SingletonService : ISingletonService
{
// 🚫 так нельзя: у Singleton в качестве зависимостей могут быть только другие Singleton
public SingletonService(IScopedService scopedService, ITransientService transientService)
{
}
}
Для использования transient и scoped сервисов внутри singleton, необходимо передать в качестве зависимости IServiceProvider и вручную вызывать нужные сервисы через GetService/CreateScope:
public class SingletonService : ISingletonService
{
private readonly IServiceProvider _serviceProvider;
// ✅ IServiceProvider - это Singleton
public SingletonService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task ExecuteWorkflow()
{
await using var scope = _serviceProvider.CreateAsyncScope();
var scopedService = scope.ServiceProvider.GetService<IScopedService>();
var transientService = scope.ServiceProvider.GetService<ITransientService>();
// workflow...
}
}
Некоторые сервисы могут реализовывать IDisposable (или IAsyncDisposable) и требовать явного освобождения ресурсов. В этом случае, для сервисов, созданных вне scope, требуется явно вызывать Dispose():
// если это многократно порождаемые IDisposable Transient сервисы, созданные "из корня"
var someService = serviceProvider.GetService<ISomeService>();
var otherService = serviceProvider.GetService<IOtherService>();
// Dispose необходимо вызывать явно
someService.Dispose();
otherService.Dispose();
Однако, если мы создаем Scope, мы можем вызвать Dispose() на нем, чтобы "задиспозить" сразу все зависимости, которые он породил:
// если сервисы создаются внутри Scope
var scope = serviceProvider.CreateScope();
var someService = scope.ServiceProvider.GetService<ISomeService>();
var otherService = scope.ServiceProvider.GetService<IOtherService>();
// Dispose скоупа таким образом так же вызовет Dispose() на всех IDisposable сервисах, которые были в нем созданы
scope.Dispose();
// 🚫 повторный вызов Dispose() в таком случае может привести к ошибке
someService.Dispose();
otherService.Dispose();
CreateAsyncScope
Для наглядности, почти во всех примерах выше, scope создавался следующим образом:
var scope = serviceProvider.CreateScope();
В современных версиях dotnet рекомендуется использовать новый метод CreateAsyncScope(), который возвращает расширенный AsyncScope, реализующий IAsyncDisposable вместо IDisposable, что улучшает работу с зависимостями, реализующими IAsyncDisposable:
await using var scope = serviceProvider.CreateAsyncScope();
Надеюсь, материал был хоть немного полезен :-)
Автор: Samidara
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/asp-net-mvc/446438
Ссылки в тексте:
[1] Источник: https://habr.com/ru/articles/1009516/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1009516
Нажмите здесь для печати.