Как работает Dependency Injection в C#

Встроенный механизм Dependency Injection в .NET основан на трех ключевых компонентах:

  1. Контейнер сервисов (IServiceCollection) — используется для регистрации зависимостей.
  2. Провайдер сервисов (IServiceProvider) — отвечает за создание экземпляров зависимостей.
  3. Область (Scope) — управляет временем жизни объектов.

Как работает регистрация зависимостей

При регистрации зависимостей в IServiceCollection создаются правила создания экземпляров. Например:

var services = new ServiceCollection();
services.AddTransient<IMyService, MyService>();
services.AddSingleton<ILogger, ConsoleLogger>();

Этот код создает коллекцию сервисов, в которой определяется, как будет создаваться IMyService и ILogger.

Создание провайдера сервисов

После регистрации сервисов контейнер должен создать IServiceProvider, который отвечает за разрешение зависимостей:

var provider = services.BuildServiceProvider();
var myService = provider.GetService<IMyService>();

Этот вызов возвращает экземпляр IMyService, используя указанную стратегию создания.

Управление областью (Scope)

Scope позволяет управлять временем жизни зависимостей. В ASP.NET Core каждый HTTP-запрос создаёт новый Scope:

using (var scope = provider.CreateScope())
{
    var scopedService = scope.ServiceProvider.GetRequiredService<IMyService>();
    scopedService.Execute();
}

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

В .NET предусмотрены три основных типа времени жизни зависимостей:

  1. Transient — новый объект создаётся при каждом запросе. services.AddTransient<IMyService, MyService>();
  2. Scoped — один объект на область (например, один HTTP-запрос). services.AddScoped<IMyService, MyService>();
  3. Singleton — один объект на всё время работы приложения. services.AddSingleton<IMyService, MyService>();

Как разрешаются зависимости

Когда IServiceProvider запрашивает зависимость, он:

  1. Ищет зарегистрированную стратегию создания объекта.
  2. Определяет, существует ли уже экземпляр (если это Singleton или Scoped).
  3. Если экземпляра нет, создаёт его, разрешая все зависимости конструктора.

Пример класса с зависимостью:

public class MyService : IMyService
{
    private readonly ILogger _logger;
    
    public MyService(ILogger logger)
    {
        _logger = logger;
    }
    
    public void Execute()
    {
        _logger.Log("Executing MyService...");
    }
}

Когда IServiceProvider создаёт MyService, он автоматически подставляет зарегистрированный ILogger.