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:
- Loose coupling β Your class doesnβt depend on a specific logger.
- Easier testing β You can pass in a fake logger in tests.
- 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