Design Patterns & Practices  

Inversion of Control vs Dependency Injection vs Dependency Inversion

When developers hear terms like IoC, DI, and DIP, confusion often arises because they sound similar and are sometimes used interchangeably. However, each concept addresses software flexibility and maintainability from a slightly different perspective. Let’s break them down.

Ioc-DI-DIP

1. Inversion of Control (IoC)

Definition

Inversion of Control is a design principle where the control of program flow is inverted compared to traditional programming. Instead of the application controlling the flow, the control is given to a framework, container, or external entity.

Example (Traditional Approach vs IoC)

Without IoC (Tight coupling):

public class UserService {
    private UserRepository _repo;

    public UserService() {
        _repo = new UserRepository(); // Service decides how to create dependency
    }
}

With IoC (Inversion of Control):

public class UserService {
    private UserRepository _repo;

    public UserService(UserRepository repo) {
        _repo = repo;  // Control of dependency creation is inverted
    }
}

Here, instead of UserService creating the UserRepository, some external controller (e.g., an IoC container) provides it.

Key Idea: IoC is about who controls the flow (developer vs framework).

2. Dependency Injection (DI)

Definition

Dependency Injection is a design pattern to implement IoC. It focuses on how dependencies are provided to a class, rather than letting the class create them internally.

Types of Dependency Injection

  1. Constructor Injection – dependencies are passed via constructor.

  2. Setter Injection – dependencies are set through properties.

  3. Interface Injection – dependencies are passed via interface methods.

Example

public interface IUserRepository {
    void Save(string userName);
}

public class SqlUserRepository : IUserRepository {
    public void Save(string userName) {
        Console.WriteLine("User saved in SQL DB");
    }
}

public class UserService {
    private readonly IUserRepository _repo;

    // Constructor Injection
    public UserService(IUserRepository repo) {
        _repo = repo;
    }

    public void Register(string userName) {
        _repo.Save(userName);
    }
}

In this case, the UserService doesn’t create SqlUserRepository. Instead, the dependency is injected from outside (e.g., via IoC container).

Key Idea: DI is a technique/pattern to achieve IoC.

3. Dependency Inversion Principle (DIP)

Definition

Dependency Inversion is the ‘D’ in SOLID principles. It states:

  • High-level modules should not depend on low-level modules.

  • Both should depend on abstractions (interfaces).

  • Abstractions should not depend on details; details should depend on abstractions.

Example

Without DIP (Tightly Coupled):

public class UserService {
    private SqlUserRepository _repo = new SqlUserRepository();

    public void Register(string userName) {
        _repo.Save(userName); // Direct dependency on low-level class
    }
}

With DIP (Loosely Coupled via Abstraction):

public interface IUserRepository {
    void Save(string userName);
}

public class SqlUserRepository : IUserRepository {
    public void Save(string userName) {
        Console.WriteLine("User saved in SQL DB");
    }
}

public class MongoUserRepository : IUserRepository {
    public void Save(string userName) {
        Console.WriteLine("User saved in MongoDB");
    }
}

public class UserService {
    private readonly IUserRepository _repo;

    public UserService(IUserRepository repo) {
        _repo = repo;  // depends on abstraction, not concrete implementation
    }

    public void Register(string userName) {
        _repo.Save(userName);
    }
}

Now, UserService can work with any repository that implements IUserRepository (SQL, Mongo, etc.).

Key Idea: DIP is a principle ensuring modules depend on abstractions, not details.

How They Relate

  • IoC → A broad principle about inverting control from the application to the framework/container.

  • DI → A concrete pattern/technique to implement IoC.

  • DIP → A principle from SOLID ensuring high-level modules depend on abstractions, not concrete implementations.

👉 In practice:

  • We apply DIP to design our classes.

  • We use DI (constructor/property injection) to provide dependencies.

  • We rely on IoC containers (like Autofac, Unity, or .NET Core’s built-in container) to manage and inject dependencies automatically.

Quick Analogy

  • IoC: You don’t cook at home; instead, you go to a restaurant where the chef controls the cooking process.

  • DI: You tell the waiter what you want (dependencies injected).

  • DIP: The restaurant menu is based on types of dishes (abstractions), not on specific ingredients (concrete details).

✅ Now, we can say:

  • "IoC is a principle, DI is a pattern to implement IoC, and DIP is a SOLID principle ensuring our design is flexible by depending on abstractions. Together, they promote loose coupling, testability, and scalability."