Ответ
Внедрение зависимостей (DI) в C# реализуется через три основных паттерна, которые определяют, как зависимость передается в потребительский класс:
-
Внедрение через конструктор (Constructor Injection)
- Самый предпочтительный и распространенный подход.
- Зависимости объявляются как
readonlyполя и передаются через параметры конструктора. -
Гарантирует, что объект будет создан в валидном состоянии (все зависимости предоставлены).
public class OrderProcessor { private readonly IOrderValidator _validator; private readonly IOrderRepository _repository; private readonly ILogger<OrderProcessor> _logger; // Зависимости явно требуются при создании public OrderProcessor( IOrderValidator validator, IOrderRepository repository, ILogger<OrderProcessor> logger) { _validator = validator ?? throw new ArgumentNullException(nameof(validator)); _repository = repository ?? throw new ArgumentNullException(nameof(repository)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public void Process(Order order) { _validator.Validate(order); _repository.Save(order); _logger.LogInformation("Order {OrderId} processed.", order.Id); } }
-
Внедрение через метод (Method Injection)
- Зависимость передается в качестве параметра конкретного метода, которому она нужна.
- Полезно, когда зависимость требуется только для одного действия и не должна храниться в состоянии класса.
public class ReportGenerator { // IReportFormatter не является частью состояния класса public string GenerateReport(ReportData data, IReportFormatter formatter) { return formatter.Format(data); } }
-
Внедрение через свойство (Property Injection / Setter Injection)
- Зависимость устанавливается через публичное свойство после создания объекта.
-
Используется реже, так как объект может какое-то время находиться в невалидном состоянии (свойство
null). Часто применяется в фреймворках (например, для внедрения в контроллеры MVC) или для опциональных зависимостей.public class NotificationService { // Опциональный логгер. Сервис может работать и без него. public ILogger<NotificationService> Logger { get; set; } public void Send(string message) { Logger?.LogDebug("Sending: {Message}", message); // ... логика отправки } }
Ключевые практики:
- Всегда предпочитайте Constructor Injection для обязательных зависимостей.
- Программируйте на уровне интерфейсов, а не конкретных реализаций.
- Регистрируйте зависимости в DI-контейнере (например, в
Startup.csилиProgram.csASP.NET Core). - Избегайте антипаттерна Service Locator, так как он скрывает зависимости и усложняет тестирование.
Ответ 18+ 🔞
Давай разберём эту тему про внедрение зависимостей, но без занудства, как есть. Представь, что ты собираешь какую-то хуёвину из деталей, и тебе нужно, чтобы тебе эти детали просто дали, а не ты их сам искал по всему заводу.
1. Внедрение через конструктор — это когда тебе всё суют в руки сразу, как только ты родился.
Самый нормальный, человеческий способ. Ты создал класс — и сразу требуешь: "Мужики, дайте мне валидатор, репозиторий и логгер, а без них я работать не буду, я сяду и буду орать". Объект сразу создаётся готовым к бою, и потом уже никто не может тебе сказать "ой, а логгер-то забыли".
public class OrderProcessor
{
private readonly IOrderValidator _validator;
private readonly IOrderRepository _repository;
private readonly ILogger<OrderProcessor> _logger;
// Смотри сюда — родился и сразу орёт: дайте три вещи, иначе ArgumentNullException!
public OrderProcessor(
IOrderValidator validator,
IOrderRepository repository,
ILogger<OrderProcessor> logger)
{
_validator = validator ?? throw new ArgumentNullException(nameof(validator));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void Process(Order order)
{
_validator.Validate(order);
_repository.Save(order);
_logger.LogInformation("Order {OrderId} processed.", order.Id);
}
}
Это как прийти в бар и сразу сказать: "Мне пиво, чипсы и пепельницу, иначе я уйду". Всё честно.
2. Внедрение через метод — это когда тебе нужна какая-то хуйня только один раз, для конкретного дела.
Зачем тащить в свой класс на всю жизнь какую-то тяжелую зависимость, если она нужна только чтобы один раз что-то отформатировать? Передал её в метод — использовал — забыл. Удобно, не захламляет класс.
public class ReportGenerator
{
// Классу вообще похуй на IReportFormatter, он живёт без него
public string GenerateReport(ReportData data, IReportFormatter formatter)
{
// Пришёл — сделал — ушёл
return formatter.Format(data);
}
}
Типа "одолжи на пять минут дрель, я тут одну дырку просверлю". Не покупать же её на всю жизнь.
3. Внедрение через свойство — это как опциональная хуйня, которая может быть, а может и не быть.
Создал объект, а потом, может быть, когда-нибудь, если захочешь — присобачишь ему какую-то функциональность через свойство. Проблема в том, что объект может какое-то время быть в некондиции, с пустым свойством. Поэтому так делают редко, обычно для всяких опциональных плюшек или когда фреймворк сам тебе что-то подсовывает.
public class NotificationService
{
// Логгер есть? — Используем. Нет? — Ну и хуй с ним, работаем без него.
public ILogger<NotificationService> Logger { get; set; }
public void Send(string message)
{
Logger?.LogDebug("Sending: {Message}", message);
// ... основная работа
}
}
Это как машина без магнитолы. Ехать-то можно, но скучно. Поставил потом — хорошо, не поставил — ну и ладно.
А теперь главное, что нужно запомнить, чтобы не быть мудаком:
- Используй внедрение через конструктор для всего, без чего твой класс — просто кусок беспомощного говна. Это главный, правильный путь.
- Работай с интерфейсами, а не с конкретными классами. Иначе ты привариваешь себе намертво колесо от "Запорожца" и потом охуеешь, когда захочешь пересадиться на "Мерседес".
- Все зависимости регистрируй в контейнере (в том же
Program.cs). Это такой главный склад, откуда все будут получать что нужно. Не регистрируешь — получишьInvalidOperationExceptionи будешь сидеть, чесать репу. - Не используй Service Locator (антипаттерн). Это когда ты не просишь зависимости явно, а лезешь в какой-то глобальный мешок и сам оттуда что-то выковыриваешь. Это пиздец как неудобно и для тестов, и для понимания, откуда что берётся. Это как вместо "дайте мне молоток" ты бежишь на склад и сам его ищешь, пока все ждут.
Вот и вся философия. Сделал зависимости явными — живи спокойно, тестируй легко, меняй реализации без боли. Сделал через жопу — получи спагетти-код, в котором через месяц сам не разберёшься. Выбирай.