Date
Dec 23, 2025
Description
about dependency injection
Published
Published
Slug
dependency-injection
when i came across this term, i thought it would be other jargon that i already have used before while developing applications.
but after using it and studying it, i found out that it has not been used by me but it is very good for scalability.
so, let me make you understand this very clearly using code and real life example.
Formal Definition:- Dependency Injection (DI) is a design pattern where an object receives its dependencies (other objects it needs to function) from an external source, rather than creating them itself, promoting loose coupling, better testability, and cleaner, more flexible code. Instead of a class building its needed components (like aCarclass building its ownEngine), theEngineobject is "injected" into theCar's constructor or methods by an "injector" or framework.
hope you have not understood it unless you know it earlier. let me explain this to you.
let say you want to build a notification system.
and initially you want to send notification via email. how will you write the code.
before i learnt dependency injection, i would have written it as
public class EmailSender{ public void Send(string message){ console.log($"Email sent: {message}"); } }
public class NotificationService{ private readonly emailSender = new EmailSender(); public void Notify(){ emailSender.Send("hi there"); } }
chatgpt
When we start writing applications, we often write code that works, but is not always easy to extend or maintain. One of the most common problems appears when we tightly couple our classes to concrete implementations.
In this post, we’ll understand Dependency Injection (DI) by building a simple Notification Service.
We’ll go through three steps:
- Implement Email notification without DI
- Add WhatsApp notification and see the problem
- Refactor using Dependency Injection
- See how .NET’s built-in DI makes everything clean and extensible
1️⃣ Notification Service Using Email (Without Dependency Injection)
Let’s start with a very simple requirement:
“Send notifications using Email.”
Email Service
publicclassEmailService { publicvoidSend(string message) { Console.WriteLine($"Sending Email: {message}"); } }
Notification Service
publicclassNotificationService { private EmailService _emailService; publicNotificationService() { _emailService =new EmailService(); } publicvoidNotify(string message) { _emailService.Send(message); } }
Usage
classProgram { staticvoidMain() { var notificationService =new NotificationService(); notificationService.Notify("Hello via Email"); } }
✅ This works perfectly fine.
But now let’s introduce a new requirement.
2️⃣ New Requirement: Support WhatsApp Notifications
Now the business says:
“We also want to send notifications via WhatsApp.”
So we create a WhatsApp service.
WhatsApp Service
publicclassWhatsAppService { publicvoidSend(string message) { Console.WriteLine($"Sending WhatsApp message: {message}"); } }
3️⃣ The Problem Without Dependency Injection
Now we want
NotificationService to support both Email and WhatsApp.A typical beginner approach looks like this:
publicclassNotificationService { private EmailService _emailService; private WhatsAppService _whatsAppService; publicNotificationService() { _emailService =new EmailService(); _whatsAppService =new WhatsAppService(); } publicvoidNotify(string message,string type) { if (type =="email") { _emailService.Send(message); } elseif (type =="whatsapp") { _whatsAppService.Send(message); } } }
Usage
var notificationService =new NotificationService(); notificationService.Notify("Hello!","email"); notificationService.Notify("Hello!","whatsapp");
❌ Problems with This Approach
This design has several issues:
1. Tight Coupling
NotificationService directly depends on:EmailService
WhatsAppService
If we add SMS, Push Notification, etc., we must modify this class again.
2. Violates Open/Closed Principle
The class is not closed for modification.
Every new notification type requires editing existing logic.
3. Hard to Test
You cannot easily mock or replace dependencies for unit testing.
4. Too Many Responsibilities
NotificationService is deciding:- how to send notifications
- which service to use
It shouldn’t care about implementation details.
At this point, the design starts to feel fragile and messy.
👉 This is where Dependency Injection comes in.
4️⃣ Introducing Dependency Injection
Instead of creating dependencies inside a class, we inject them from outside.
Key idea:
Depend on abstractions, not concrete implementations.
Step 1: Create an Interface
publicinterfaceINotificationService { voidSend(string message); }
Step 2: Implement Email Notification
publicclassEmailNotificationService :INotificationService { publicvoidSend(string message) { Console.WriteLine($"Sending Email: {message}"); } }
Step 3: Implement WhatsApp Notification
publicclassWhatsAppNotificationService :INotificationService { publicvoidSend(string message) { Console.WriteLine($"Sending WhatsApp message: {message}"); } }
Step 4: Refactor Notification Sender Using DI
Now
NotificationService will depend on the interface, not concrete classes.publicclassNotificationSender { privatereadonly INotificationService _notificationService; publicNotificationSender(INotificationService notificationService) { _notificationService = notificationService; } publicvoidNotify(string message) { _notificationService.Send(message); } }
✅ No
new EmailService()✅ No
if/else✅ Fully extensible
5️⃣ Using Dependency Injection in .NET
.NET provides a built-in DI container.
Example using Microsoft.Extensions.DependencyInjection
using Microsoft.Extensions.DependencyInjection; classProgram { staticvoidMain() { var services =new ServiceCollection(); // Register dependency services.AddTransient<INotificationService, EmailNotificationService>(); // Register consumer services.AddTransient<NotificationSender>(); var serviceProvider = services.BuildServiceProvider(); var notifier = serviceProvider.GetRequiredService<NotificationSender>(); notifier.Notify("Hello using Dependency Injection!"); } }
Switching to WhatsApp Is Now EASY 🎉
Just change one line:
services.AddTransient<INotificationService, WhatsAppNotificationService>();
No changes needed in:
NotificationSender
- business logic
- existing code
✅ Final Benefits of Dependency Injection
- ✅ Loose coupling
- ✅ Easy to extend
- ✅ Easy to test
- ✅ Clean architecture
- ✅ Follows SOLID principles
- ✅ Works perfectly with .NET built-in DI
Conclusion
Without Dependency Injection:
- Code becomes tightly coupled
- Hard to extend
- Hard to maintain
With Dependency Injection:
- Your system becomes flexible
- New features plug in easily
- Your code follows best practices