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:

  • OrderProcessor directly creates EmailService

  • 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:

  • OrderProcessor depends 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

  1. Loose coupling

  2. Easy unit testing

  3. Replace implementations easily

  4. Better scalability

  5. 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.