Dependency Injection in C#
Decouple classes from concrete dependencies by registering services in .NET's built-in dependency injection container and injecting abstractions through constructors.
Part 1: What You Will Learn
Decouple classes from concrete dependencies by registering services in .NET's built-in dependency injection container and injecting abstractions through constructors.
- Define a small service interface and a concrete implementation.
- Inject the interface into a consuming class through its constructor.
- Register services with `IServiceCollection`.
- Understand Singleton, Scoped, and Transient lifetimes.
Project setup: Create a .NET 10 Console App and install the NuGet package `Microsoft.Extensions.DependencyInjection`.
Part 2: Topic-Specific Working Example
The following example is written specifically for this lesson. Create the project described above, enter the code, run it, and then change some values to observe how the feature behaves.
using Microsoft.Extensions.DependencyInjection;
public interface IMessageSender
{
void Send(string recipient, string message);
}
public sealed class EmailSender : IMessageSender
{
public void Send(string recipient, string message)
{
Console.WriteLine($"Email to {recipient}: {message}");
}
}
public sealed class OrderService(IMessageSender sender)
{
public void ConfirmOrder(string email, int orderId)
{
sender.Send(email, $"Order #{orderId} is confirmed.");
}
}
ServiceCollection services = new();
services.AddSingleton<IMessageSender, EmailSender>();
services.AddTransient<OrderService>();
using ServiceProvider provider = services.BuildServiceProvider();
OrderService orderService = provider.GetRequiredService<OrderService>();
orderService.ConfirmOrder("customer@example.com", 1042);Part 3: How the Code Works
- `OrderService` depends on `IMessageSender`, not directly on `EmailSender`, so the implementation can be replaced.
- `AddSingleton` creates one EmailSender for the lifetime of the service provider.
- `AddTransient` creates a new OrderService each time it is requested.
- Constructor injection makes dependencies explicit and makes the class much easier to test.
Part 4: Mini Project & Practice
Mini project: add an SmsSender implementation and register it instead of EmailSender. Then create a test implementation that records messages rather than sending them.
Tip: Type the code yourself in Visual Studio 2026, run it, then deliberately change one part at a time. The goal is to understand the feature rather than simply copy the finished example.
When you are comfortable with this lesson, continue to Lesson 21.
C# in Visual Studio 2026
📘 This lesson is part of the book C# in Visual Studio 2026 by Dr. Liew Voon Kiong.
View on Amazon Kindle Edition