Dependency Inversion Principle in C#: Flipping Dependencies for Cleaner Architecture
In most apps, business logic ends up relying directly on infrastructureâfile systems, APIs, databases. Thatâs fine⊠until it isnât.
Ever tried to unit test a service that talks straight to SQL Server? Or change a logger and had to rewrite half your app?
Thatâs where the Dependency Inversion Principle (DIP) comes inâthe âDâ in SOLID.
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
Letâs break this down without overcomplicating it.
What It Means
At its core, Dependency Inversion is about decoupling.
Instead of this:
Business Logic â Logger
We flip it:
Business Logic â ILogger â Logger
Now your logic doesnât care how something is loggedâit just knows it can call ILogger.Log()
. The actual implementation? Plug it in later.
The Wrong Way: Tight Coupling
Letâs say you have this:
public class InvoiceService
{
private readonly FileLogger _logger = new FileLogger();
public void Process(Invoice invoice)
{
_logger.Log("Processing invoice...");
// logic here
}
}
This works, but now:
- You canât reuse
InvoiceService
with a different logger. - Itâs harder to test.
- It violates DIPâ
InvoiceService
depends directly on the concreteFileLogger
.
â The Better Way: Invert the Dependency
Letâs extract an interface:
public interface ILogger
{
void Log(string message);
}
Now have FileLogger
implement it:
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[FILE] {message}");
}
}
Then inject the abstraction:
public class InvoiceService
{
private readonly ILogger _logger;
public InvoiceService(ILogger logger)
{
_logger = logger;
}
public void Process(Invoice invoice)
{
_logger.Log("Processing invoice...");
}
}
Youâve now inverted the dependency. InvoiceService
depends only on what it needsâa logging contract, not a concrete file-based logger.
Now Itâs Testable
You can easily mock or fake the logger in a unit test:
public class FakeLogger : ILogger
{
public void Log(string message) { }
}
var service = new InvoiceService(new FakeLogger());
service.Process(testInvoice);
DIP makes testing cleaner and fasterâno hacks, no tightly coupled dependencies.
Real-World Uses
- Swap databases without rewriting services
- Switch between email/SMS/Slack notifications without touching business logic
- Plug in different payment gateways behind the same interface
DIP isnât about patterns for the sake of patternsâitâs about protecting your core logic from infrastructure churn.
đŹ Final Thoughts
Dependency Inversion isnât just a fancy design principleâitâs a mindset shift.
When you stop wiring your core logic directly to low-level implementations, you get systems that are easier to test, easier to refactor, and a whole lot easier to trust.
So the next time youâre about to tie your logic directly to a file system, database, or external serviceâpause.
And ask yourself: should this depend on an abstraction instead?
Good architecture doesnât depend on the database, the logger, or the framework. It depends on design that doesnât care.
đ Thanks for Following the SOLID Series
From single responsibility to dependency inversionâif youâve made it this far, your code is already feeling lighter.
Write less regret. Ship more joy. Stay SOLID. đȘ
In case you missed them: