Dependency Injection Container for C# (.NET): Complete Guide
Dependency Injection (DI) is one of the key principles of modern C# (.NET) development, allowing you to create loosely coupled, testable, and easily maintainable applications. In the .NET ecosystem, the primary tool for implementing DI is the built-in container from the Microsoft.Extensions.DependencyInjection package. In this article, we will thoroughly examine what a DI container is, how to configure it, what lifetimes exist, and how to use it correctly in real-world projects.
What is a Dependency Injection Container?
A Dependency Injection container (or IoC container) is a software component that automatically manages the creation and injection of object dependencies. Instead of manually creating objects using new, you register types in the container, and it handles their creation and wiring.
Main advantages of using a DI container:
- Reduced code coupling — classes depend on abstractions (interfaces), not concrete implementations.
- Simplified testing — dependencies can be easily replaced with mock objects.
- Centralized lifecycle management — you control when objects are created and destroyed.
- Increased flexibility — implementations can be changed without modifying consumer code.
Installing and Configuring a DI Container
In modern .NET projects (ASP.NET Core, Console, Worker Services), the DI container is already built-in. To use it, simply add the package to your project:
dotnet add package Microsoft.Extensions.DependencyInjectionAfter that, an instance of IServiceCollection is created, into which all dependencies are registered. Let's look at a simple example of service registration:
using Microsoft.Extensions.DependencyInjection;
// Define an interface and its implementationpublic interface IMessageService{ void Send(string message);}
public class EmailService : IMessageService{ public void Send(string message) { Console.WriteLine($"Email sent: {message}"); }}
// Configure the containervar services = new ServiceCollection();services.AddTransient<IMessageService, EmailService>();
// Build the service providervar serviceProvider = services.BuildServiceProvider();
// Get an instance of the servicevar messageService = serviceProvider.GetService<IMessageService>();messageService.Send("Hello from DI!");Dependency Lifetimes
The DI container supports three main modes for managing object lifetimes:
- Transient (AddTransient) — a new instance is created for every request. Suitable for lightweight, stateless services.
- Scoped (AddScoped) — one instance per scope (e.g., per HTTP request in ASP.NET Core). Ideal for services with state within a single operation.
- Singleton (AddSingleton) — a single instance for the entire application. Used for caches, loggers, configurations.
Example of registration with different lifetimes:
services.AddTransient<ITransientService, TransientService>();services.AddScoped<IScopedService, ScopedService>();services.AddSingleton<ISingletonService, SingletonService>();Important: if a service depends on another service with a shorter lifetime, it can lead to errors (e.g., a Singleton that uses a Scoped service). Such situations are called "captive dependencies" and should be avoided.
Constructor Injection
The most common way to inject dependencies is through the constructor. The container automatically resolves all depend
Also in library
proptest for Rust: A Complete Guide with Examples
Working with Markdown in Go (Golang): Libraries, Parsing, and Conversion
Data Validation in Go (Golang): Libraries, Approaches, and Code Examples