Back to Blog

dependency injection

5 min read
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 a Car class building its own Engine), the Engine object is "injected" into the Car'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:
  1. Implement Email notification without DI
  1. Add WhatsApp notification and see the problem
  1. Refactor using Dependency Injection
  1. 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