← rupesh singh

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
  2. Add WhatsApp notification and see the problem
  3. Refactor using Dependency Injection
  4. 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

public class EmailService
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending Email: {message}");
    }
}

Notification Service

public class NotificationService
{
    private EmailService _emailService;

    public NotificationService()
    {
        _emailService = new EmailService();
    }

    public void Notify(string message)
    {
        _emailService.Send(message);
    }
}

Usage

class Program
{
    static void Main()
    {
        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

public class WhatsAppService
{
    public void Send(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:

public class NotificationService
{
    private EmailService _emailService;
    private WhatsAppService _whatsAppService;

    public NotificationService()
    {
        _emailService = new EmailService();
        _whatsAppService = new WhatsAppService();
    }

    public void Notify(string message, string type)
    {
        if (type == "email")
        {
            _emailService.Send(message);
        }
        else if (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

public interface INotificationService
{
    void Send(string message);
}

Step 2: Implement Email Notification

public class EmailNotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending Email: {message}");
    }
}

Step 3: Implement WhatsApp Notification

public class WhatsAppNotificationService : INotificationService
{
    public void Send(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.

public class NotificationSender
{
    private readonly INotificationService _notificationService;

    public NotificationSender(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void Notify(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;

class Program
{
    static void Main()
    {
        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