Monday, 14 April 2025

CSharp Corner C#.NET : Dependency Injection

 πŸ’‘ What is Dependency Injection?


Dependency Injection is a way to give an object what it needs (its dependencies) instead of letting it create those things by itself.


 

πŸ§ƒ Real-life example (to make it stick, easy to remember):

Imagine you're at a juice shop.

You want a Mango Juice, but you don't make the juice yourself.

You just ask the shopkeeper, and they give you the juice.

    • You = The class (like OrderProcessor)
    • Mango Juice = The dependency (like IMangoJuiceMaker)
    • Shopkeeper = The Dependency Injection system

You don't care how the juice is made, only that it's given to you ready to use.

 

🧱 In code terms:

Suppose a class needs something to workβ€”say, a logger:


public class OrderService

{

    private readonly ILogger _logger;


    public OrderService(ILogger logger) // Dependency Injection here!

    {

        _logger = logger;

    }


    public void PlaceOrder()

    {

        _logger.Log("Order placed!");

    }

}


Instead of creating the logger like this:


_logger = new ConsoleLogger(); // Bad! Hard to test, hard to change


You're injecting it from outside.


 

🎯 Why this is awesome:

  1. Loose coupling β€“ Your class doesn’t depend on a specific logger.
  2. Easier testing β€“ You can pass in a fake logger in tests.
  3. More flexibility β€“ You can swap dependencies easily.

 

🧰 How it's usually done in C# (especially with ASP.NET Core):

The built-in DI system is used.

In Startup.cs (or Program.cs in newer versions):


services.AddScoped<ILogger, ConsoleLogger>();


services.AddScoped<OrderService>();


Then in your app, when you ask for OrderService, it will automatically get an ILogger injected into it.

 

🧠 Remember it like this:

β€œDon’t make what you needβ€”ask for it. Let someone else give it to you.”

 



Here’s a tiny full working example of Dependency Injection in C# using the console app style.
No web stuff β€” just the basics so it's super clear.

 

βœ… Step-by-step example: Logging an order

 

1. Create an interface (the contract for logging):

public interface ILogger

{

    void Log(string message);

}


 

2. Create a class that implements the interface:

public class ConsoleLogger : ILogger

{

    public void Log(string message)

    {

        Console.WriteLine($"LOG: {message}");

    }

}


 

3. Create the main class that depends on ILogger:

public class OrderService

{

    private readonly ILogger _logger;


    // Dependency Injection happens here via constructor

    public OrderService(ILogger logger)

    {

        _logger = logger;

    }


    public void PlaceOrder()

    {

        _logger.Log("Order has been placed!");

    }

}


 

4. Set up DI container in Program.cs (like the shopkeeper):

using Microsoft.Extensions.DependencyInjection;


class Program

{

    static void Main(string[] args)

    {

        // 1. Create a service collection (the DI container)

        var serviceCollection = new ServiceCollection();


        // 2. Register services

        serviceCollection.AddTransient<ILogger, ConsoleLogger>();

        serviceCollection.AddTransient<OrderService>();


        // 3. Build the service provider

        var serviceProvider = serviceCollection.BuildServiceProvider();


        // 4. Get OrderService with all dependencies injected

        var orderService = serviceProvider.GetService<OrderService>();


        // 5. Use it

        orderService.PlaceOrder();


    }

}

 

πŸ”„ What happens here?

  • You register that ILogger should be satisfied by ConsoleLogger.
  • When you ask for OrderService, the DI container:
    • sees it needs an ILogger,
    • finds that it should use ConsoleLogger,
    • creates everything and wires it up.

 

🧠 How to remember:

β€œInstead of creating your tools, you let the toolbox hand them to you, already ready.”

 




What is the ServiceCollection, AddTransient, BuildServiceProvider, GetService in the above example of Dependency Injection in C#.



Let’s break each of these down so they make total sense.

 

🧱 1. ServiceCollection

Think of ServiceCollection as your toolbox where you register all the things (services) your app might need.

var serviceCollection = new ServiceCollection();

This creates a container where you say:

    • β€œHey, if someone needs an ILogger, give them a ConsoleLogger.”
    • β€œIf someone needs an OrderService, create one for them.”

 

πŸ” 2. AddTransient<TInterface, TImplementation>()

This is how you register a service in the container.

serviceCollection.AddTransient<ILogger, ConsoleLogger>();

This means:

β€œWhenever someone asks for an ILogger, create a new ConsoleLogger each time.”

There are 3 common types:

    • AddTransient = New instance every time
    • AddScoped = New instance per request (used in web apps)
    • AddSingleton = One instance for the whole app

For console apps, AddTransient works great.

 

πŸ”¨ 3. BuildServiceProvider()

Once you've registered all your services, this method builds the DI container so you can start using it.


var serviceProvider = serviceCollection.BuildServiceProvider();


This gives you a ServiceProvider, which is like a factory that knows how to create any registered object and its dependencies.

 

πŸ“¦ 4. GetService<T>()

This is how you ask the DI system for something.

var orderService = serviceProvider.GetService<OrderService>();

When you do this, the DI system:

    • Looks at OrderService
    • Sees it needs an ILogger
    • Finds that ConsoleLogger was registered for ILogger
    • Creates a ConsoleLogger, then uses it to create OrderService
    • Returns a fully built OrderService object

 

🧠 Super simple summary:

Code

What it means

ServiceCollection

A list of what services are available (like a registration list)

AddTransient<A, B>()

β€œIf someone asks for A, give them a new B”

BuildServiceProvider()

Lock in all the services and prepare the system

GetService<T>()

β€œHey container, give me an instance of T, fully set up”

 



This is super important β€” especially when your services hold resources like files, database connections, or network sockets. How to ensure all the objects created by the DI container (ServiceCollection) are properly cleaned up (disposed) ?

 

 

βœ… Short Answer:

Use a using block with the ServiceProvider, because it implements IDisposable.


using (var serviceProvider = serviceCollection.BuildServiceProvider())

{

    var orderService = serviceProvider.GetService<OrderService>();

    orderService.PlaceOrder();

} // πŸ’₯ Everything gets cleaned up here


When the using block ends, the Dispose() method is called on the ServiceProvider, which in turn disposes of all services that were created and implement IDisposable.

 



πŸ”„ What gets cleaned up?

Only the services that:

    • Were created by the container
    • AND implement IDisposable

Examples:

    • DbContext
    • HttpClient
    • File streams
    • Custom classes you wrote that implement IDisposable

 

πŸ›‘ Don't do this (if you're disposing manually):

var sp = serviceCollection.BuildServiceProvider();

sp.Dispose(); // works, but you might forget this β€” using block is safer

 


πŸ‘€ Bonus Tip: Use using var for even cleaner code (C# 8.0+):

using var serviceProvider = serviceCollection.BuildServiceProvider();

var orderService = serviceProvider.GetService<OrderService>();

orderService.PlaceOrder();

 


No comments:

Post a Comment