Работали ли вы с IoC-контейнерами (Dependency Injection)?

«Работали ли вы с IoC-контейнерами (Dependency Injection)?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, активно применяю Dependency Injection (DI) и IoC-контейнеры для создания слабосвязанного, тестируемого и поддерживаемого кода. Основной опыт — со встроенным контейнером ASP.NET Core, а также с Autofac.

Основные концепции и практики:

  • Типы жизненного цикла:

    • Transient: Создается новый экземпляр на каждый запрос к контейнеру. Подходит для легковесных, stateless сервисов.
    • Scoped: Один экземпляр создается на область видимости (например, на один HTTP-запрос в веб-приложении). Стандартный выбор для контекстных сервисов, таких как DbContext в EF Core.
    • Singleton: Один экземпляр на все время жизни приложения. Используется для общих ресурсов (кэш, конфигурация).
  • Регистрация сервисов в ASP.NET Core:

    public void ConfigureServices(IServiceCollection services)
    {
        // Регистрация по интерфейсу
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddSingleton<ILogger, FileLogger>();
        services.AddTransient<IEmailService, EmailService>();
    
        // Регистрация с фабрикой
        services.AddScoped<IService>(sp => 
            new ConcreteService(sp.GetRequiredService<IDependency>())
        );
    }

Преимущества использования DI:

  1. Тестируемость: Зависимости легко подменить моками в unit-тестах.
  2. Гибкость: Изменение реализации сервиса требует правок только в точке регистрации.
  3. Управление жизненным циклом: Контейнер автоматически управляет созданием и освобождением ресурсов.
  4. Улучшение архитектуры: Код следует принципу Dependency Inversion (D из SOLID).

Пример внедрения в конструктор:

public class OrderProcessor
{
    private readonly IPaymentGateway _paymentGateway;
    private readonly IOrderRepository _repository;

    // Зависимости автоматически разрешаются контейнером
    public OrderProcessor(IPaymentGateway paymentGateway, IOrderRepository repository)
    {
        _paymentGateway = paymentGateway;
        _repository = repository;
    }
    // ... методы, использующие зависимости
}