Жизненный цикл зависимостей

Последнее обновление: 15.11.2022

Встроенный механизм внедрения зависимостей .NET позволяет управлять жизненным циклом сервисов. С точки зрения жизненного цикла сервисы могут представлять один из следующих типов:

  • Transient: при каждом обращении к сервису создается новый объект сервиса

  • Scoped: создается отдельный контекст или scope, и в рамках этого контекста при всех обращениях у сервису будет использоваться один и тот же объект сервиса.

  • Singleton: объект сервиса создается при первом обращении к нему, все последующих обращениях используется один и тот же ранее созданный объект сервиса

Для создания каждого типа сервиса предназначен соответствующий метод AddTransient(), AddScoped() и AddSingleton().

Для рассмотрения механизма внедрения зависимостей и жизненного цикла возьмем следующие типы:

public interface ICounter
{
    int Value { get; }
}
public class RandomCounter : ICounter
{
    static Random rnd = new Random();
    private int _value;
    public RandomCounter() => _value = rnd.Next(0, 1000000);
    public int Value => _value;
}

Интерфейс ICounter представляет сервис, который в свойстве Value хранит некоторое число. Реализацию этого интерфейса - класс RandomCounter использует класс Random для генерации случайного числа в диапазоне от 0 до 1000000 и возвращает это число из свойства Value.

Теперь на примере этих типов рассмотрим уравление жизненным циклом сервисов.

AddTransient

Метод AddTransient() создает transient-объекты. Такие объекты создаются при каждом обращении к ним. Данный метод имеет ряд перегруженных версий:

  • AddTransient(Type serviceType)

  • AddTransient(Type serviceType, Type implementationType)

  • AddTransient(Type serviceType, Func<IServiceProvider,object> implementationFactory)

  • AddTransient<TService>()

  • AddTransient<TService, TImplementation>()

  • AddTransient<TService>(Func<IServiceProvider,TService> implementationFactory)

  • AddTransient<TService, TImplementation>(Func<IServiceProvider,TImplementation> implementationFactory)

Используем данный метод для добавления сервисов:

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection()
    .AddTransient<ICounter, RandomCounter>();

using ServiceProvider serviceProvider = services.BuildServiceProvider();

PrintCounters();
PrintCounters();

void PrintCounters()
{
    var counter1 = serviceProvider.GetService<ICounter>();
    var counter2 = serviceProvider.GetService<ICounter>();
    Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}");
}

public interface ICounter
{
    int Value { get; }
}
public class RandomCounter : ICounter
{
    static Random rnd = new Random();
    private int _value;
    public RandomCounter() => _value = rnd.Next(0, 1000000);
    public int Value => _value;
}

Для большей показательности я вынес логику по получению сервиса в отдельный метод PrintCounters. Причем в каждом методе два раза получаем сервис ICounter и выводим на консоль значение его свойства Value. Консольный вывод программы в моем случае:

Counter1: 939614; Counter2: 862579
Counter1: 741773; Counter2: 730321

Как видно, в программе идет 4 обращения к провайдеру сервисов для получения сервиса ICounter. И при каждом обращении создается свой объект RandomCounter. Соответственно во всех четырех случаях случайные числа будут разные.

AddSingleton

Для сервисов, которые добавляются с помощью метода AddSingleton() создается один объект для всех последующих обращений. Этот метод имеет все те же перегруженые версии, что и AddTransient. Для применения AddSingleton изменим код приложения:

using Microsoft.Extensions.DependencyInjection;

IServiceCollection services = new ServiceCollection()
    .AddSingleton<ICounter, RandomCounter>();

using ServiceProvider serviceProvider = services.BuildServiceProvider();

PrintCounters();
PrintCounters();
void PrintCounters()
{
    var counter1 = serviceProvider.GetService<ICounter>();
    var counter2 = serviceProvider.GetService<ICounter>();
    Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}");
}

public interface ICounter
{
    int Value { get; }
}
public class RandomCounter : ICounter
{
    static Random rnd = new Random();
    private int _value;
    public RandomCounter() => _value = rnd.Next(0, 1000000);
    public int Value => _value;
}

Здесь та же самая логика, только теперь сервис ICounter определен как синглтон, соответственно при каждом обращении к провайдеру сервисов получим один и тот же объект ICounter:

Counter1: 650427; Counter2: 650427
Counter1: 650427; Counter2: 650427

AddScoped

Для сервисов, которые добавляются методом AddScoped, будет создаваться один экземпляр объекта для одного контекста или scope. Здесь под контекстом понимает область видимости объекта IServiceScope. Для получения этого объекта у ServiceProvider вызывается метод CreateScope():

using ServiceProvider serviceProvider = services.BuildServiceProvider();
using (IServiceScope scope = serviceProvider.CreateScope())
{
    // контекст IServiceScope
}

Например, изменим добавление сервиса, применив метод AddScoped():

using Microsoft.Extensions.DependencyInjection;

IServiceCollection services = new ServiceCollection()
    .AddScoped<ICounter, RandomCounter>();

using ServiceProvider serviceProvider = services.BuildServiceProvider();

PrintCounters();
PrintCounters();
void PrintCounters()
{
    using IServiceScope scope = serviceProvider.CreateScope(); // контекст scope
    var counter1 = scope.ServiceProvider.GetService<ICounter>();
    var counter2 = scope.ServiceProvider.GetService<ICounter>();
    Console.WriteLine($"Counter1: {counter1?.Value}; Counter2: {counter2?.Value}");
}

public interface ICounter
{
    int Value { get; }
}
public class RandomCounter : ICounter
{
    static Random rnd = new Random();
    private int _value;
    public RandomCounter() => _value = rnd.Next(0, 1000000);
    public int Value => _value;
}

Здесь контекст одного объекта IServiceScope ограничивается методом PrintCounters(). Для получения сервиса в пределах этого контекста сначлаа получаем у scope провайдер сервиса и через него получаем сервис ICounter:

var counter1 = scope.ServiceProvider.GetService<ICounter>();

Поскольку здесь метод PrintCounters вызывается два раза, соответственно будут создаваться два разных контекста. И в рамках каждого контекста мы будем получать свой объект ICounter:

Counter1: 620832; Counter2: 620832
Counter1: 816555; Counter2: 816555
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850