C sharp - Dependency Injection (DI) & Inversion of Control (IoC) in C#
Dependency Injection and Inversion of Control are architectural principles that help build loosely coupled, testable, and maintainable systems.
They are fundamental in modern C# applications, especially in ASP.NET Core.
1. The Problem: Tight Coupling
Example without DI:
class EmailService
{
public void Send() => Console.WriteLine("Email Sent");
}
class OrderProcessor
{
private EmailService _emailService = new EmailService();
public void Process()
{
_emailService.Send();
}
}
Problem:
-
OrderProcessordirectly createsEmailService -
Hard to test
-
Cannot replace EmailService easily
-
Violates SOLID principles
This is tight coupling.
2. Inversion of Control (IoC)
Normally:
Class controls creation of its dependencies.
IoC:
Control of dependency creation is moved outside the class.
Meaning:
Instead of class creating dependency,
dependency is provided from outside.
3. Dependency Injection (DI)
Dependency Injection is a way to implement IoC.
The dependency is injected into the class rather than created inside it.
Improved Version with DI
interface IEmailService
{
void Send();
}
class EmailService : IEmailService
{
public void Send() => Console.WriteLine("Email Sent");
}
class OrderProcessor
{
private readonly IEmailService _emailService;
public OrderProcessor(IEmailService emailService)
{
_emailService = emailService;
}
public void Process()
{
_emailService.Send();
}
}
Now:
-
OrderProcessordepends on abstraction (interface) -
Not concrete implementation
This follows:
Dependency Inversion Principle (DIP)
4. Types of Dependency Injection
1. Constructor Injection (Most Common)
Dependency passed via constructor.
Best practice:
Ensures dependency is mandatory.
2. Property Injection
public IEmailService EmailService { get; set; }
Less safe (dependency may not be set).
3. Method Injection
Dependency passed to method when needed.
5. Dependency Injection Container
In large applications, manually wiring dependencies is difficult.
So we use DI container (IoC container).
Example in ASP.NET Core:
builder.Services.AddScoped<IEmailService, EmailService>();
Then framework automatically injects it:
public OrderProcessor(IEmailService emailService)
The container:
-
Creates objects
-
Manages lifecycle
-
Resolves dependencies
6. Service Lifetimes in .NET
When registering services:
| Lifetime | Meaning |
|---|---|
| Transient | New instance every time |
| Scoped | One per request |
| Singleton | Single instance for entire app |
Choosing wrong lifetime can cause:
-
Memory leaks
-
Thread issues
-
Unexpected behavior
7. Benefits of DI
-
Loose coupling
-
Easy unit testing
-
Replace implementations easily
-
Better scalability
-
Follows SOLID principles
Example:
For testing:
class FakeEmailService : IEmailService
{
public void Send() { }
}
Inject fake service into OrderProcessor.
No real email is sent during test.
8. IoC vs DI Difference
| IoC | DI |
|---|---|
| Design principle | Implementation technique |
| Inverts control | Injects dependencies |
| Broad concept | Specific method |
DI is one way to achieve IoC.
9. Common DI Containers in .NET
-
Built-in ASP.NET Core container
-
Autofac
-
Ninject
-
StructureMap
Most modern projects use built-in container.
10. Real-World Example
In ASP.NET Core controller:
public class OrderController
{
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
}
Controller does not create service.
Framework injects it.
This enables:
-
Clean architecture
-
Microservices
-
Modular systems
11. Why DI is Critical in Enterprise Systems
Large systems require:
-
Swappable modules
-
Testable components
-
Independent services
-
Maintainable architecture
Without DI:
System becomes rigid and hard to scale.