Встречается на 38% собеседований по C#

Что такое dependency injection

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

Как это работает

Вместо того чтобы класс сам создавал свои зависимости (например, через new), он получает их через конструктор, свойство или метод. Наиболее распространённый способ — внедрение через конструктор: класс объявляет зависимости как параметры конструктора, а внешний код (или контейнер) предоставляет конкретные реализации.

Пример без DI:

csharp
public class OrderService
{
    private readonly ILogger _logger = new FileLogger(); // жёсткая привязка к FileLogger
}

Пример с DI:

csharp
public class OrderService
{
    private readonly ILogger _logger;

    public OrderService(ILogger logger) // зависимость внедряется извне
    {
        _logger = logger;
    }
}

В первом случае OrderService жёстко привязан к FileLogger, что затрудняет замену логирования и тестирование. Во втором случае можно передать любую реализацию ILogger — например, ConsoleLogger или mock-объект в тестах.

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

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

Реализация в C#

В C# DI часто реализуется через встроенный контейнер зависимостей — IServiceCollection (используется в ASP.NET Core). Регистрация зависимостей происходит при запуске приложения, а затем контейнер автоматически создаёт объекты с учётом их зависимостей.

Пример регистрации в ASP.NET Core:

csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ILogger, FileLogger>(); // регистрируем реализацию
    services.AddScoped<OrderService>(); // контейнер сам внедрит ILogger
}

Также существуют сторонние контейнеры, такие как Autofac или Ninject, которые предоставляют дополнительные возможности (например, автоматическое связывание по соглашениям).

Подводные камни

  • Чрезмерное использование: DI не нужен для каждой мелочи; иногда проще создать объект внутри класса.
  • Сложность конфигурации: при большом количестве зависимостей конфигурация контейнера может стать громоздкой.
  • Скрытые зависимости: если использовать внедрение через свойства, зависимости могут быть неочевидны; конструктор — более явный способ.
  • Время жизни объектов: неправильная настройка времени жизни (например, singleton для объекта с состоянием) может привести к ошибкам.

Когда использовать

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

Коротко

  • DI — это передача зависимостей извне, а не создание их внутри класса.
  • Основные способы: конструктор, свойство, метод; конструктор — предпочтительный.
  • Преимущества: слабая связность, лёгкое тестирование, гибкость замены реализаций.
  • В C# DI реализуется через IServiceCollection или сторонние контейнеры (Autofac, Ninject).
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы