← Назад к вопросам

Что такое DI в С#?

2.0 Middle🔥 181 комментариев
#Dependency Injection и IoC

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Dependency Injection (DI) в C#?

Dependency Injection (DI) — это архитектурный паттерн проектирования, который реализует принцип Inversion of Control (IoC) — "инверсия управления". Его основная цель — отделить создание зависимостей объектов от их бизнес-логики, что делает код более модульным, тестируемым и сопровождаемым.

Суть паттерна

Вместо того чтобы класс самостоятельно создавал свои зависимости (например, через оператор new), эти зависимости "внедряются" в него извне — обычно через конструктор, свойства или методы. Это позволяет:

  • Ослабить связность (low coupling) между компонентами
  • Упростить замену реализаций (например, мок-объектов в тестах)
  • Централизовать управление жизненным циклом объектов

Пример без DI и с DI

Рассмотрим типичный пример сервиса отправки уведомлений.

Без использования DI (жёсткая связь):

public class EmailService
{
    public void Send(string message)
    {
        // Логика отправки email
    }
}

public class NotificationService
{
    private EmailService _emailService;
    
    public NotificationService()
    {
        // Проблема: класс сам создаёт зависимость
        _emailService = new EmailService();
    }
    
    public void Notify(string message)
    {
        _emailService.Send(message);
    }
}

С использованием DI через конструктор:

public interface INotificationService
{
    void Notify(string message);
}

public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        // Логика отправки email
    }
}

public class NotificationService : INotificationService
{
    private readonly IMessageSender _sender;
    
    // Зависимость внедряется извне
    public NotificationService(IMessageSender sender)
    {
        _sender = sender;
    }
    
    public void Notify(string message)
    {
        _sender.Send(message);
    }
}

Типы внедрения зависимостей

В C# существуют три основных способа внедрения зависимостей:

  1. Внедрение через конструктор (Constructor Injection) — наиболее предпочтительный способ

    public class MyService
    {
        private readonly IDependency _dependency;
        
        public MyService(IDependency dependency)
        {
            _dependency = dependency;
        }
    }
    
  2. Внедрение через свойства (Property Injection) — когда зависимость необязательна

    public class MyService
    {
        public IDependency Dependency { get; set; }
    }
    
  3. Внедрение через методы (Method Injection) — когда зависимость нужна только для одного метода

    public class MyService
    {
        public void Process(IDependency dependency)
        {
            // Использование dependency
        }
    }
    

Контейнеры зависимостей в .NET

Для управления зависимостями в реальных приложениях используются IoC-контейнеры, которые автоматизируют процесс внедрения. В современном .NET основным является встроенный контейнер:

// Регистрация зависимостей в Program.cs или Startup.cs
var builder = WebApplication.CreateBuilder(args);

// Регистрируем сервисы
builder.Services.AddScoped<IMessageSender, EmailSender>();
builder.Services.AddTransient<INotificationService, NotificationService>();

var app = builder.Build();

// Контейнер автоматически внедрит EmailSender в NotificationService
var notificationService = app.Services.GetRequiredService<INotificationService>();

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

В .NET DI контейнере существует три основных жизненных цикла:

  • Transient — новый экземпляр создаётся при каждом запросе

    services.AddTransient<IMyService, MyService>();
    
  • Scoped — один экземпляр на область видимости (например, на HTTP-запрос в веб-приложениях)

    services.AddScoped<IMyService, MyService>();
    
  • Singleton — один экземпляр на всё время работы приложения

    services.AddSingleton<IMyService, MyService>();
    

Преимущества DI

  1. Улучшенная тестируемость — легко подменять реальные зависимости мок-объектами

    [Test]
    public void NotificationServiceTest()
    {
        // Arrange
        var mockSender = new Mock<IMessageSender>();
        var service = new NotificationService(mockSender.Object);
        
        // Act
        service.Notify("Test");
        
        // Assert
        mockSender.Verify(x => x.Send("Test"), Times.Once);
    }
    
  2. Гибкость и расширяемость — замена реализации без изменения кода потребителя

  3. Упрощение конфигурации — централизованное управление зависимостями

  4. Улучшение читаемости кода — явное объявление зависимостей через конструктор

  5. Соблюдение принципов SOLID — особенно принципа единственной ответственности и инверсии зависимостей

Практическое применение

В современных ASP.NET Core приложениях DI является неотъемлемой частью фреймворка. Все основные компоненты — контроллеры, сервисы, репозитории — регистрируются и разрешаются через встроенный контейнер зависимостей. Это позволяет создавать хорошо структурированные, поддерживаемые приложения, которые легко адаптировать к изменяющимся требованиям.

Dependency Injection — это не просто технический приём, а философия проектирования, которая способствует созданию чистого, модульного и тестируемого кода, что особенно важно в долгосрочной перспективе разработки сложных приложений.

Что такое DI в С#? | PrepBro