DI и IoC: зачем и как работает контейнер
↑ 3.2 Dependency Injection · → Следующая
Зачем это на собесе
Базовый вопрос: «что такое DI, чем отличается от IoC и зачем контейнер». Дальше почти всегда идут времена жизни.
Объяснение
- IoC (Inversion of Control) — принцип: не класс создаёт свои зависимости, а внешний код передаёт их.
- DI (Dependency Injection) — способ реализации IoC: зависимости передаются через конструктор (предпочтительно), свойство или параметр метода.
- DI-контейнер — инструмент, который по регистрациям строит граф объектов и управляет временем жизни.
// Без DI: жёсткая связь
public class OrderService { private readonly SqlRepo _repo = new SqlRepo(); }
// С DI: зависимость от абстракции
public class OrderService(IOrderRepository repo, ILogger<OrderService> log) { }
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddScoped<OrderService>();| Выгода | Пояснение |
|---|---|
| Слабая связность | реализация заменяется без правки клиента |
| Тестируемость | подставляются fake/stub |
| Единое место сборки | композиционный корень в Program.cs |
| Управление временем жизни | контейнер создаёт и освобождает объекты |
Как работает встроенный контейнер
ServiceProvider находит конструктор с максимальным числом разрешимых параметров, рекурсивно строит зависимости, кэширует экземпляры по времени жизни, вызывает Dispose у созданных им IDisposable при закрытии scope. Поддерживает open generics: AddScoped(typeof(IRepo<>), typeof(Repo<>)) и IEnumerable<T> для нескольких реализаций.
Нюансы и подводные камни
- Constructor injection лучше property injection: зависимости явны и обязательны.
- Service Locator (
IServiceProvider.GetServiceв бизнес-коде) — антипаттерн, скрывает зависимости. - Много параметров конструктора — сигнал нарушения SRP.
- Встроенный контейнер не умеет property injection, interceptors и автоматические декораторы (для этого Autofac/Scrutor).
- Циклические зависимости приводят к исключению при построении.
Практика
- Уберите
newиз сервиса и зарегистрируйте зависимости. - Зарегистрируйте несколько реализаций интерфейса и получите
IEnumerable<T>. - Напишите open generic репозиторий.
Вопросы с ответами
Чем DI отличается от IoC?
IoC — принцип инверсии управления; DI — конкретный приём передачи зависимостей извне.
Зачем нужен DI-контейнер, если можно вручную передавать зависимости?
На больших графах контейнер убирает шаблонный код, управляет временем жизни и освобождением объектов.
Что такое композиционный корень?
Место, где строится граф зависимостей (Program.cs); остальной код не знает о контейнере.