¿Qué es la Inyección de Dependencias en C# y para qué sirve?
En el desarrollo moderno con C# y .NET, uno de los principios clave de diseño es Dependency Injection (DI) — la inyección de dependencias. Este patrón permite crear código débilmente acoplado, testeable y fácil de mantener. En lugar de que una clase cree por sí misma las instancias de los objetos que necesita (acoplamiento fuerte), las dependencias se inyectan desde el exterior.
El objetivo principal de DI es implementar el principio de Inversion of Control (IoC), donde el control de la creación de objetos se transfiere a un contenedor o framework externo. Esto cambia radicalmente la arquitectura de la aplicación, haciéndola flexible y preparada para cambios.
El problema del acoplamiento fuerte (Tight Coupling)
Consideremos un ejemplo típico sin DI:
public class OrderService{ private readonly EmailSender _emailSender;
public OrderService() { // Dependencia fuerte — creamos una instancia de una clase concreta _emailSender = new EmailSender(); }
public void ProcessOrder(Order order) { // Lógica de procesamiento del pedido _emailSender.Send("Pedido procesado"); }}Aquí, OrderService depende directamente de EmailSender. Si queremos reemplazar el envío de email por SMS o logging, tendríamos que modificar el código de OrderService. Esto viola el Open/Closed Principle y hace que las pruebas unitarias sean prácticamente imposibles.
Formas principales de inyectar dependencias en C#
En C#, existen tres formas principales de implementar DI. Cada una tiene sus propios escenarios de uso y características.
1. Inyección por constructor (Constructor Injection)
Esta es la forma más popular y recomendada en .NET. Las dependencias se pasan a través de los parámetros del constructor y generalmente se guardan en campos privados de solo lectura (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;
// La dependencia se inyecta a través del constructor public OrderService(INotificationService notificationService) { _notificationService = notificationService; }
public void ProcessOrder(Order order) { // Lógica... _notificationService.Send("Pedido procesado"); }}Ventajas: se ve claramente qué dependencias necesita la clase; las dependencias son inmutables después de crear el objeto; es fácil de testear usando objetos mock.
2. Inyección por propiedad (Property Injection)
La dependencia se establece a través de una propiedad pública (setter). Se usa con menos frecuencia, cuando la dependencia no es obligatoria para el funcionamiento de la clase.
public class OrderService{ public INotificationService NotificationService { get; set; }
public OrderService() { }
public void ProcessOrder(Order order) { if (NotificationService != null) { NotificationService.Send("Pedido procesado"); } }}Desventaja: se puede olvidar establecer la dependencia, lo que provocará un NullReferenceException en tiempo de ejecución. Se recomienda usar solo para dependencias opcionales.
3. Inyección por método (Method Injection)
La dependencia se pasa directamente al método donde se utiliza. Esto es útil cuando la dependencia solo se necesita para una acción específica.
public class OrderService{ public void ProcessOrder(Order order, INotificationService notificationService) { // Lógica... notificat