Inyección de Dependencias en C#: guía sencilla para principiantes
La Inyección de Dependencias (DI) es uno de los patrones de diseño clave en el desarrollo moderno con C# y .NET. Si apenas estás comenzando tu camino en la programación, el término puede parecer complejo, pero en realidad es mucho más sencillo. En este artículo, analizaremos qué es la Inyección de Dependencias, para qué sirve y cómo usarla en la práctica.
¿Qué es la Inyección de Dependencias en palabras simples?
Imagina que estás armando una computadora. Puedes comprar una torre ya ensamblada, donde todo está soldado y no se puede reemplazar. O puedes armarla con componentes individuales: procesador, tarjeta gráfica, memoria RAM. El segundo enfoque es mucho más flexible: puedes cambiar fácilmente la tarjeta gráfica por una más potente o agregar otro módulo de memoria.
La Inyección de Dependencias funciona bajo el mismo principio. En lugar de "soldar" rígidamente las dependencias dentro de una clase (crear objetos directamente en el código), las "inyectas" desde el exterior. Esto hace que tu código sea flexible, comprobable y fácil de mantener.
Veamos un mal ejemplo sin DI:
public class EmailService{ private SmtpClient _client; public EmailService() { // Acoplamiento rígido: la clase crea la dependencia por sí misma _client = new SmtpClient("smtp.example.com", 587); } public void Send(string to, string message) { _client.Send(to, message); }}Ahora el mismo ejemplo usando DI:
public class EmailService{ private readonly ISmtpClient _client; // La dependencia se inyecta a través del constructor public EmailService(ISmtpClient client) { _client = client; } public void Send(string to, string message) { _client.Send(to, message); }}La diferencia es evidente: en el segundo caso trabajamos a través de la interfaz ISmtpClient, y la implementación concreta se pasa desde el exterior.
Formas principales de inyectar dependencias en C#
En C# existen tres formas principales de implementar DI. Veamos cada una de ellas.
1. Inyección por constructor
Es la forma más popular y recomendada. Las dependencias se pasan a través de los parámetros del constructor y se almacenan en campos privados.
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("Procesamiento del pedido iniciado"); _emailService.Send(order.CustomerEmail, "Su pedido ha sido recibido"); _logger.Log("Procesamiento del pedido finalizado"); }}2. Inyección por propiedad
La dependencia se establece a través de una propiedad pública. Esta forma es útil cuando la dependencia es opcional.
public class ReportGenerator{ public ILogger Logger { get; set; } public void GenerateReport() { Logger?.Log("Generando informe..."); // Lógica de generación }}3. Inyección por método
La dependencia se pasa directamente al método donde se necesita. Generalmente se usa con poca frecuencia, pero puede ser útil en algunos escenarios.
public class DataProcessor{ public void ProcessData(string data, IValidator validator) { if (validator.Validate(data)) { // Procesamiento de datos } }}Ejemplo práctico: de código malo a código bueno
Veamos cómo DI puede mejorar el código real. Supongamos que tenemos una clase para trabajar con la base de datos.
Código malo (sin DI):
public class UserRepository{ private SqlConnect