Dependency Injection em C#: o que é e para que serve
Dependency Injection (DI) é um dos principais padrões de design que permite criar aplicações em C# com baixo acoplamento e fáceis de testar. Em vez de a classe criar suas próprias dependências (por exemplo, objetos para trabalhar com banco de dados ou logging), essas dependências são passadas (injetadas) de fora. Isso implementa o princípio Inversion of Control (IoC) — o controle é transferido para um contêiner externo.
No ecossistema .NET, o DI se tornou o padrão de fato a partir do ASP.NET Core. O contêiner de dependências embutido (através de Microsoft.Extensions.DependencyInjection) permite registrar e resolver dependências facilmente. Neste artigo, vamos abordar a teoria, exemplos práticos e melhores práticas de uso do DI em C#.
Princípios básicos do Dependency Injection
O DI se baseia em três formas básicas de injeção de dependências. Vamos analisar cada uma delas.
1. Injeção via construtor
A forma mais comum e preferida. As dependências são passadas através dos parâmetros do construtor da classe.
public interface ILogger{ void Log(string message);}
public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine(message);}
public class OrderService{ private readonly ILogger _logger;
// Injeção de dependência via construtor public OrderService(ILogger logger) { _logger = logger; }
public void ProcessOrder(int orderId) { _logger.Log($"Processando pedido {orderId}"); // lógica de negócio }}2. Injeção via propriedade
A dependência é definida através de uma propriedade pública. Menos preferível, pois torna a dependência opcional e pode levar a erros em tempo de execução.
public class OrderService{ public ILogger Logger { get; set; }
public void ProcessOrder(int orderId) { if (Logger == null) throw new InvalidOperationException("Logger não foi definido"); Logger.Log($"Processando pedido {orderId}"); }}3. Injeção via método
A dependência é passada como parâmetro do método. Usada raramente, geralmente para dependências temporárias.
public class OrderService{ public void ProcessOrder(int orderId, ILogger logger) { logger.Log($"Processando pedido {orderId}"); }}Registro de dependências no contêiner .NET
No .NET moderno (ASP.NET Core, Console, Worker Services), é usado o contêiner DI embutido. O registro ocorre no método ConfigureServices ou ao criar um ServiceCollection.
Existem três tempos de vida (lifetime) para serviços registrados:
- Transient — uma nova instância é criada a cada solicitação. Adequado para serviços leves e de curta duração.
- Scoped — uma instância por requisição HTTP (ou escopo). Usado para Entity Framework DbContext.
- Singleton — uma instância para toda a aplicação. Adequado para caches, configurações, loggers.
// Exemplo para ASP.NET Corepublic class Startup{ public void ConfigureServices(IServiceCollection services) { // Registro com interface services.AddTransient<ILogger, ConsoleLogger>(); services.AddScoped<IOrderRepository, OrderRepository>(); services.AddSingleton<IConfiguration, ConfigurationManager>();
// Registro sem interface (classe concreta) services.AddTransient<OrderService>(); }}
// Uso no controllerpublic class OrderController : ControllerBase{ private readonly OrderService _orderService;
public OrderController(OrderServi