Dependency Injection in C#: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is Dependency Injection in C# and Why You Need It



In modern C# and .NET development, one of the key design principles is Dependency Injection (DI). This pattern allows you to create loosely coupled, testable, and easily maintainable code. Instead of a class independently creating instances of the objects it needs (tight coupling), dependencies are injected from the outside.



The main goal of DI is to implement the Inversion of Control (IoC) principle, where the control of object creation is transferred to an external container or framework. This fundamentally changes the application architecture, making it flexible and ready for change.



The Problem of Tight Coupling



Consider a typical example without DI:



public class OrderService{    private readonly EmailSender _emailSender;

public OrderService() { // Hard dependency — creating an instance of a concrete class _emailSender = new EmailSender(); }

public void ProcessOrder(Order order) { // Order processing logic _emailSender.Send("Order processed"); }}


Here, OrderService directly depends on EmailSender. If we want to replace email sending with SMS or logging, we would have to modify the OrderService code. This violates the Open/Closed Principle and makes unit testing practically impossible.



Main Ways to Implement Dependency Injection in C#



In C#, there are three main ways to implement DI. Each has its own use cases and characteristics.



1. Constructor Injection



This is the most popular and recommended method in .NET. Dependencies are passed through constructor parameters and are usually stored in private, read-only fields (readonly).



public interface INotificationService{    void Send(string message);}

public class EmailService : INotificationService{ public void Send(string message) { Console.WriteLine($"Email sent: {message}"); }}

public class OrderService{ private readonly INotificationService _notificationService;

// Dependency is injected through the constructor public OrderService(INotificationService notificationService) { _notificationService = notificationService; }

public void ProcessOrder(Order order) { // Logic... _notificationService.Send("Order processed"); }}


Advantages: it is clearly visible which dependencies the class needs; dependencies are immutable after the object is created; easy to test by substituting mock objects.



2. Property Injection



The dependency is set through a public property (setter). It is used less frequently, when the dependency is not mandatory for the class to function.



public class OrderService{    public INotificationService NotificationService { get; set; }

public OrderService() { }

public void ProcessOrder(Order order) { if (NotificationService != null) { NotificationService.Send("Order processed"); } }}


Disadvantage: you might forget to set the dependency, which will lead to a NullReferenceException at runtime. It is recommended to use this only for optional dependencies.



3. Method Injection



The dependency is passed directly into the method where it is used. This is convenient when the dependency is only needed for one specific action.



public class OrderService{    public void ProcessOrder(Order order, INotificationService notificationService)    {        // Logic...        notification                

Blogs

Book Recommendations