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

Ответ

Dependency Injection (DI) — это паттерн проектирования, при котором зависимости объекта (сервисы, которые он использует) предоставляются ему извне, а не создаются внутри самого объекта. Это делает код слабосвязанным, тестируемым и легким для поддержки.

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

  1. Внедрение через конструктор (Constructor Injection) — самый распространенный и рекомендуемый способ.
  2. Внедрение через свойство (Property Injection).
  3. Внедрение через метод (Method Injection).

Пример Constructor Injection:

// 1. Интерфейс зависимости
public interface ILogger
{
    void Log(string message);
}

// 2. Конкретная реализация
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine($"[LOG]: {message}");
}

// 3. Класс, который зависит от ILogger. Зависимость внедряется через конструктор.
public class OrderProcessor
{
    private readonly ILogger _logger;

    // Конструктор получает зависимость извне
    public OrderProcessor(ILogger logger)
    {
        _logger = logger; // Внедрение зависимости
    }

    public void ProcessOrder(Order order)
    {
        // Бизнес-логика...
        _logger.Log("Order processed successfully.");
    }
}

// 4. Использование (обычно конфигурируется в DI-контейнере)
ILogger logger = new ConsoleLogger();
var processor = new OrderProcessor(logger); // Зависимость "внедрена"
processor.ProcessOrder(new Order());

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

  • Слабая связность: Классы не зависят от конкретных реализаций, а только от абстракций (интерфейсов).
  • Упрощенное тестирование: Легко подменить реальную зависимость (например, доступ к базе данных) на mock-объект в юнит-тестах.
  • Гибкость и поддерживаемость: Изменение реализации зависимости (например, замена логгера) требует правок только в одном месте — конфигурации DI-контейнера.

В экосистеме .NET DI является фундаментальной частью ASP.NET Core и реализуется через встроенный контейнер (Microsoft.Extensions.DependencyInjection) или сторонние библиотеки (Autofac, Ninject).

Ответ 18+ 🔞

А, Dependency Injection! Ну это ж классика, её каждый второй джуниор в резюме пишет, а на деле половина нихуя не понимает, как оно работает. Сейчас разжуем, но без соплей.

Смотри, Dependency Injection — это по-русски «внедрение зависимостей». Суть проще пареной репы: если твоему классу для работы нужен какой-то сервис (например, логгер или доступ к базе), ты не должен внутри класса этот сервис создавать через new. Это тупо и негибко. Вместо этого ты говоришь: «Эй, дай-ка мне сюда эту штуку, я без неё не могу», и тебе её подают со стороны. Как в столовой — ты не готовишь, тебе наливают.

Как это обычно делают? Три основных способа:

  1. Через конструктор (Constructor Injection) — самый правильный и частый. Все зависимости прилетают сразу при рождении объекта.
  2. Через свойство (Property Injection) — менее строгий, зависимость можно впендюрить потом.
  3. Через метод (Method Injection) — когда зависимость нужна только для одного конкретного дела.

Вот смотри, живой пример на конструкторах:

// 1. Абстракция. Говорим "мне нужен кто-то, кто умеет логировать"
public interface ILogger
{
    void Log(string message);
}

// 2. Конкретная реализация. Вот этот парень умеет
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine($"[LOG]: {message}");
}

// 3. А вот наш главный класс, который без логгера — как без рук
public class OrderProcessor
{
    private readonly ILogger _logger; // Храним зависимость тут

    // КОНСТРУКТОР: сюда нам обязаны передать логгер, иначе объект не создать
    public OrderProcessor(ILogger logger)
    {
        _logger = logger; // Вот оно, внедрение! Нам ПОДАЛИ зависимость.
    }

    public void ProcessOrder(Order order)
    {
        // Делаем свои дела...
        _logger.Log("Заказ обработан, всё чики-пуки."); // Используем
    }
}

// 4. Собираем всё в кучу где-то снаружи
ILogger logger = new ConsoleLogger(); // Создаём сервис
var processor = new OrderProcessor(logger); // И ВПИХИВАЕМ его в процессор
processor.ProcessOrder(new Order());

А нахуя это всё? Преимущества-то какие?

  • Слабая связность: Твой OrderProcessor не привязан намертво к ConsoleLogger. Захотел писать в файл — подсунул другую реализацию ILogger. Главное, чтобы интерфейс совпадал. Класс становится как швейцарский нож — универсальный.
  • Тестирование — одни плюшки. Хочешь протестировать OrderProcessor? Подсовываешь ему в конструктор не настоящий логгер, а заглушку (mock), которая нихуя не делает, и спокойно проверяешь бизнес-логику. Никаких реальных записей в базу или консоль.
  • Гибкость и порядок. Всё управление зависимостями собирается в одном месте (обычно в корне приложения или в конфигурации DI-контейнера). Меняешь реализацию в одном месте — и она автоматом прокатывается везде. Никакого поиска по всему коду, где там new FileLogger() написали.

В .NET, особенно в ASP.NET Core, это вообще основа основ. Там есть встроенный DI-контейнер, который сам, как заправский официант, разносит зависимости по классам. Ты только говоришь: «слушай, если кто попросит ILogger, дай ему вот этот ConsoleLogger», и он всё сделает. Красота, а не паттерн.