Dependency Injection в C#: простое руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Dependency Injection в C#: простое руководство для начинающих

Dependency Injection (DI) — это один из ключевых паттернов проектирования в современной разработке на C# и .NET. Если вы только начинаете свой путь в программировании, термин может показаться сложным, но на самом деле всё гораздо проще. В этой статье мы разберём, что такое Dependency Injection, зачем он нужен и как его использовать на практике.

Что такое Dependency Injection простыми словами?

Представьте, что вы собираете компьютер. Вы можете купить готовый системный блок, в котором всё уже впаяно и не подлежит замене. Или вы можете собрать его из отдельных компонентов: процессора, видеокарты, оперативной памяти. Второй подход гораздо гибче — вы можете легко заменить видеокарту на более мощную или добавить ещё планку памяти.

Dependency Injection работает по тому же принципу. Вместо того чтобы жёстко "впаивать" зависимости внутрь класса (создавать объекты прямо в коде), вы "внедряете" их извне. Это делает ваш код гибким, тестируемым и легко поддерживаемым.

Рассмотрим плохой пример без DI:

public class EmailService
{
    private SmtpClient _client;
    
    public EmailService()
    {
        // Жёсткая связь — класс сам создаёт зависимость
        _client = new SmtpClient("smtp.example.com", 587);
    }
    
    public void Send(string to, string message)
    {
        _client.Send(to, message);
    }
}

Теперь тот же пример с использованием DI:

public class EmailService
{
    private readonly ISmtpClient _client;
    
    // Зависимость внедряется через конструктор
    public EmailService(ISmtpClient client)
    {
        _client = client;
    }
    
    public void Send(string to, string message)
    {
        _client.Send(to, message);
    }
}

Разница очевидна: во втором случае мы работаем через интерфейс ISmtpClient, а конкретную реализацию передаём извне.

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

В C# существует три основных способа реализации DI. Рассмотрим каждый из них.

1. Внедрение через конструктор

Это самый популярный и рекомендуемый способ. Зависимости передаются через параметры конструктора и сохраняются в приватные поля.

public class OrderService
{
    private readonly IEmailService _emailService;
    private readonly ILogger _logger;
    
    public OrderService(IEmailService emailService, ILogger logger)
    {
        _emailService = emailService;
        _logger = logger;
    }
    
    public void ProcessOrder(Order order)
    {
        _logger.Log("Обработка заказа начата");
        _emailService.Send(order.CustomerEmail, "Ваш заказ принят");
        _logger.Log("Обработка заказа завершена");
    }
}

2. Внедрение через свойство

Зависимость устанавливается через публичное свойство. Этот способ удобен, когда зависимость является опциональной.

public class ReportGenerator
{
    public ILogger Logger { get; set; }
    
    public void GenerateReport()
    {
        Logger?.Log("Генерация отчёта...");
        // Логика генерации
    }
}

3. Внедрение через метод

Зависимость передаётся непосредственно в метод, где она нужна. Обычно используется редко, но может быть полезен в некоторых сценариях.

public class DataProcessor
{
    public void ProcessData(string data, IValidator validator)
    {
        if (validator.Validate(data))
        {
            // Обработка данных
        }
    }
}

Практический пример: от плохого к хорошему коду

Давайте посмотрим, как DI может улучшить реальный код. Предположим, у нас есть класс для работы с базой данных.

Плохой код (без DI):

public class UserRepository
{
    private SqlConnection _connection;
    
    public UserRepository()
    {
        // Жёсткая привязка к SQL Server
        _connection = new SqlConnection("connection_string");
    }
    
    public User GetUser(int id)
    {
        // Логика получения пользователя
    }
}

Хороший код (с DI):

public interface IDatabaseConnection
{
    void Open();
    void Close();
    // Другие методы
}

public class UserRepository
{
    private readonly IDatabaseConnection _connection;
    
    public UserRepository(IDatabaseConnection connection)
    {
        _connection = connection;
    }
    
    public User GetUser(int id)
    {
        _connection.Open();
        // Логика получения пользователя
        _connection.Close();
    }
}

Теперь мы можем легко заменить SQL Server на PostgreSQL или использовать in-memory базу данных для тестов.

Использование DI-контейнера в .NET

В современных .NET приложениях (ASP.NET Core, Worker Services) DI уже встроен в фреймворк. Вам нужно только зарегистрировать свои сервисы в Startup или Program.cs.

// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);

// Регистрация сервисов
builder.Services.AddScoped();
builder.Services.AddTransient();
builder.Services.AddSingleton();

var app = builder.Build();

Три основных времени жизни сервисов:

  • Transient — новый экземпляр создаётся каждый раз при запросе
  • Scoped — один экземпляр на каждый HTTP-запрос (веб-приложения)
  • Singleton — один экземпляр на всё приложение

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

Подведём итоги: почему стоит использовать DI в ваших проектах?

  • Тестируемость — вы легко можете подменить реальные зависимости на моки (заглушки) в юнит-тестах
  • Гибкость — замена реализации не требует изменения кода классов-потребителей
  • Поддерживаемость — код становится более модульным и понятным
  • Слабая связанность — классы зависят от абстракций, а не от конкретных реализаций
  • Упрощение управления жизненным циклом — DI-контейнер сам заботится о создании и уничтожении объектов

Заключение

Dependency Injection — это не просто модный паттерн, а мощный инструмент для создания качественного, тестируемого и поддерживаемого кода на C#. Начните с малого — внедряйте зависимости через конструктор, используйте интерфейсы и постепенно осваивайте DI-контейнеры.

Помните: хороший код — это код, который легко читать, тестировать и изменять. Dependency Injection помогает достичь всех этих целей. Практикуйтесь, и вы быстро уви

Блоги

Рекомендаций по книгам