Implementing Dependency Injection in C#: Building Loosely Coupled and Testable Applications

Learn the principles and techniques of dependency injection (DI) in C#. This tutorial explains different DI approaches—constructor injection, property injection, and method injection—and demonstrates their application in creating more modular, testable, and maintainable C# applications.



Dependency Injection in C#

Dependency Injection (DI) is a design pattern that promotes loose coupling in your code. Instead of a class creating its own dependencies, they are provided from the outside (injected). This improves code organization, testability, and maintainability.

Types of Dependency Injection in C#

C# supports three main types of dependency injection:

  1. Constructor Injection
  2. Property Injection
  3. Method Injection

1. Constructor Injection

Dependencies are provided through a class's constructor. This ensures that all required dependencies are available when the object is created. It promotes better testability and encapsulation.


public interface IEmailService { void SendEmail(string to, string subject, string body); }
public class EmailService : IEmailService { public void SendEmail(string to, string subject, string body) { ... } }
public class NotificationService {
    private readonly IEmailService _emailService;
    public NotificationService(IEmailService emailService) { _emailService = emailService; }
    // ...
}

2. Property Injection

Dependencies are provided through public properties. This allows you to set dependencies after an object is created, offering more flexibility than constructor injection.


public interface ILogger { void Log(string message); }
public class ConsoleLogger : ILogger { public void Log(string message) { ... } }
public class MyClass {
    public ILogger Logger { get; set; }
    public void MyMethod() { Logger.Log("Something happened!"); }
}

3. Method Injection

Dependencies are passed as parameters to a method. This provides fine-grained control; you inject dependencies only when a method is called, rather than when the entire object is created.


public class MyClass {
    public void MyMethod(ILogger logger) {
        logger.Log("Something happened!");
    }
}