Dependency Injection контейнер для C# (.NET) — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Dependency Injection контейнер для C# (.NET): полное руководство

Dependency Injection (DI) — это один из ключевых принципов современной разработки на C# (.NET), который позволяет создавать слабосвязанные, тестируемые и легко поддерживаемые приложения. В экосистеме .NET основным инструментом для реализации DI является встроенный контейнер из пакета Microsoft.Extensions.DependencyInjection. В этой статье мы подробно разберём, что такое DI контейнер, как его настроить, какие жизненные циклы существуют и как правильно использовать его в реальных проектах.

Что такое Dependency Injection контейнер?

Dependency Injection контейнер (или IoC-контейнер) — это программный компонент, который автоматически управляет созданием и внедрением зависимостей объектов. Вместо того чтобы создавать объекты вручную с помощью new, вы регистрируете типы в контейнере, а он сам заботится о их создании и связывании.

Основные преимущества использования DI контейнера:

  • Уменьшение связанности кода — классы зависят от абстракций (интерфейсов), а не от конкретных реализаций.
  • Упрощение тестирования — легко подменять зависимости на mock-объекты.
  • Централизованное управление жизненным циклом — вы контролируете, когда объекты создаются и уничтожаются.
  • Повышение гибкости — можно менять реализации без изменения кода потребителей.

Установка и настройка DI контейнера

В современных проектах .NET (ASP.NET Core, Console, Worker Services) DI контейнер уже встроен. Для его использования достаточно добавить пакет в проект:

dotnet add package Microsoft.Extensions.DependencyInjection

После этого создаётся экземпляр IServiceCollection, в который регистрируются все зависимости. Рассмотрим простой пример регистрации сервисов:

using Microsoft.Extensions.DependencyInjection;

// Определяем интерфейс и его реализацию
public interface IMessageService
{
    void Send(string message);
}

public class EmailService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

// Настройка контейнера
var services = new ServiceCollection();
services.AddTransient<IMessageService, EmailService>();

// Строим провайдер сервисов
var serviceProvider = services.BuildServiceProvider();

// Получаем экземпляр сервиса
var messageService = serviceProvider.GetService<IMessageService>();
messageService.Send("Hello from DI!");

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

DI контейнер поддерживает три основных режима управления временем жизни объектов:

  • Transient (AddTransient) — каждый запрос создаёт новый экземпляр. Подходит для легковесных сервисов без состояния.
  • Scoped (AddScoped) — один экземпляр на область (например, на HTTP-запрос в ASP.NET Core). Идеально для сервисов с состоянием в рамках одной операции.
  • Singleton (AddSingleton) — единственный экземпляр на всё приложение. Используется для кэшей, логгеров, конфигураций.

Пример регистрации с разными жизненными циклами:

services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddSingleton<ISingletonService, SingletonService>();

Важно: если сервис зависит от другого сервиса с более коротким жизненным циклом, это может привести к ошибкам (например, Singleton, который использует Scoped). Такие ситуации называются "captive dependencies" и их следует избегать.

Внедрение зависимостей через конструктор

Самый распространённый способ внедрения — через конструктор. Контейнер автоматически разрешает все зависимости, указанные в параметрах конструктора:

public class OrderService
{
    private readonly IMessageService _messageService;
    private readonly ILogger<OrderService> _logger;

    public OrderService(IMessageService messageService, ILogger<OrderService> logger)
    {
        _messageService = messageService;
        _logger = logger;
    }

    public void ProcessOrder(int orderId)
    {
        _logger.LogInformation("Processing order {Id}", orderId);
        _messageService.Send($"Order {orderId} processed");
    }
}

// Регистрация
services.AddScoped<IOrderService, OrderService>();

Регистрация нескольких реализаций и фабрики

Иногда требуется зарегистрировать несколько реализаций одного интерфейса. Для этого используется TryAddEnumerable или прямое добавление:

services.AddTransient<IReportProvider, PdfReportProvider>();
services.AddTransient<IReportProvider, ExcelReportProvider>();

// Получение всех реализаций
var providers = serviceProvider.GetServices<IReportProvider>();

Для более

Рекомендаций