C#DotNetDependency InjectionIoCInversion of ControlDependency Inversion PrincipleDIPSOLID Principles
From Tightly Coupled to Loosely Coupled Classes
October 6, 20262 min read
🔴 Tightly Coupled Classes
👉 Step 1
C#
public class FileLogger
{
public void Log(string message)
{
Console.WriteLine("File Log: " + message);
}
}
public class CustomerService
{
private FileLogger logger = new FileLogger(); // ❌ tightly coupled
public void AddCustomer(string name)
{
logger.Log($"Customer {name} added.");
}
}
//Program.cs
service.AddCustomer("Hridu");❌ Problem: CustomerService is tightly coupled to FileLogger. Cannot change the logger easily.
⚙️ Step 2: Implement IoC using Factory Pattern
Control object creation outside the main class.
C#
public class FileLogger
{
public void Log(string message)
{
Console.WriteLine("File Log: " + message);
}
}
public class LoggerFactory
{
public static FileLogger CreateLogger()
{
return new FileLogger();
}
}
public class CustomerService
{
private FileLogger logger;
public CustomerService()
{
logger = LoggerFactory.CreateLogger(); // ✅ IoC via factory
}
public void AddCustomer(string name)
{
logger.Log($"Customer {name} added.");
}
}
//Program.cs
service.AddCustomer("Hridu");✅ Better — object creation is delegated to a factory, not hardcoded.
📐 Step 3: Implement DIP by creating abstraction
C#
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine("File Log: " + message);
}
}
public class CustomerService
{
private ILogger logger; //Now CustomerService depends on abstraction, not implementation:
public CustomerService()
{
logger = new FileLogger(); // ❌ still manually creating (but using abstraction now)
}
public void AddCustomer(string name)
{
logger.Log($"Customer {name} added.");
}
}✅ DIP applied: High-level class depends on ILogger, not FileLogger.
🔄 Step 4: Implement DI (Dependency Injection)
C#
public class CustomerService
{
private readonly ILogger logger;
// ✅ Constructor Injection
public CustomerService(ILogger logger)
{
this.logger = logger;
}
public void AddCustomer(string name)
{
logger.Log($"Customer {name} added.");
}
}
// Usage:
ILogger logger = new FileLogger();
CustomerService service = new CustomerService(logger);
service.AddCustomer("Hridu");✅ Class no longer creates the dependency — it receives it. This makes it testable, flexible, and decoupled.
✅ Step 5: Use IoC Container (e.g., .NET Core)
📦 Register in Program.cs (or Main method for console app):
C#
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddScoped<ILogger, FileLogger>(); // Register abstraction and implementation
services.AddScoped<CustomerService>(); // Register service
var provider = services.BuildServiceProvider();
var service = provider.GetRequiredService<CustomerService>();
service.AddCustomer("Hridu");✅ You now use an IoC container to manage dependencies — full Inversion of Control.
🟢 Result: Loosely Coupled Classes
| ✅ Feature | Achieved By |
|---|---|
| No hardcoded dependencies | IoC / DI |
| Easy to change logger types | DIP + Interface |
| Testable with mock loggers | DI |
| Pluggable and extendable | IoC container |