O que é Dependency Injection em C# e para que serve
No desenvolvimento moderno com C# e .NET, um dos princípios-chave de design é o Dependency Injection (DI) — injeção de dependências. Esse padrão permite criar código fracamente acoplado, testável e facilmente sustentável. Em vez de a classe criar por si mesma instâncias dos objetos de que necessita (acoplamento rígido), as dependências são injetadas externamente.
O objetivo principal do DI é implementar o princípio Inversion of Control (IoC), onde o controle da criação de objetos é transferido para um contêiner ou framework externo. Isso muda radicalmente a arquitetura da aplicação, tornando-a flexível e pronta para mudanças.
O problema do acoplamento rígido (Tight Coupling)
Vamos considerar um exemplo típico sem DI:
public class OrderService{ private readonly EmailSender _emailSender;
public OrderService() { // Dependência rígida — criamos uma instância de uma classe concreta _emailSender = new EmailSender(); }
public void ProcessOrder(Order order) { // Lógica de processamento do pedido _emailSender.Send("Pedido processado"); }}Aqui, OrderService depende diretamente de EmailSender. Se quisermos substituir o envio de email por SMS ou logging, teremos que alterar o código de OrderService. Isso viola o Open/Closed Principle e torna o teste unitário praticamente impossível.
Principais formas de injeção de dependências em C#
Em C#, existem três formas principais de implementar DI. Cada uma tem seus cenários de uso e características específicas.
1. Injeção via construtor (Constructor Injection)
Esta é a forma mais popular e recomendada no .NET. As dependências são passadas através dos parâmetros do construtor e geralmente armazenadas em campos privados, acessíveis apenas para leitura (readonly).
public interface INotificationService{ void Send(string message);}
public class EmailService : INotificationService{ public void Send(string message) { Console.WriteLine($"Email enviado: {message}"); }}
public class OrderService{ private readonly INotificationService _notificationService;
// A dependência é injetada através do construtor public OrderService(INotificationService notificationService) { _notificationService = notificationService; }
public void ProcessOrder(Order order) { // Lógica... _notificationService.Send("Pedido processado"); }}Vantagens: fica claro quais dependências a classe precisa; as dependências são imutáveis após a criação do objeto; fácil de testar, substituindo por objetos mock.
2. Injeção via propriedade (Property Injection)
A dependência é definida através de uma propriedade pública (setter). É usada com menos frequência, quando a dependência não é obrigatória para o funcionamento da classe.
public class OrderService{ public INotificationService NotificationService { get; set; }
public OrderService() { }
public void ProcessOrder(Order order) { if (NotificationService != null) { NotificationService.Send("Pedido processado"); } }}Desvantagem: pode-se esquecer de definir a dependência, o que resultará em NullReferenceException durante a execução. Recomenda-se usar apenas para dependências opcionais.
3. Injeção via método (Method Injection)
A dependência é passada diretamente no método onde é usada. Isso é conveniente quando a dependência é necessária apenas para uma ação específica.
public class OrderService{ public void ProcessOrder(Order order, INotificationService notificationService) { // Lógica... notificat