.NET Dependency Injection Lifetimes Explained
A clear and practical guide to the three main service lifetimes in .NET dependency injection: Transient, Scoped, and Singleton. Understand the difference and when to use each one.
Dependency Injection (DI) is at the heart of the modern ASP.NET Core framework. It's a powerful pattern for building loosely coupled and testable applications. A crucial part of using the built-in DI container effectively is understanding service lifetimes.
When you register a service, you have to tell the container how to manage its lifecycle. Should it create a new instance every time it's requested? Or should it reuse the same instance? This is what lifetimes control. There are three main lifetimes in .NET: Transient, Scoped, and Singleton.
Let's break them down.
1. Transient
- Lifetime: A new instance is created every time the service is requested.
- Registration:
builder.Services.AddTransient<IMyService, MyService>();
When to use it: Transient is the best choice for lightweight, stateless services. If a service doesn't hold any state (i.e., it has no fields or properties that change during its lifetime) and is cheap to create, Transient is a safe and correct default.
Example Use Case: A simple calculator service that performs calculations but doesn't store any previous results.
public interface ICalculator
{
int Add(int a, int b);
}
public class Calculator : ICalculator
{
public int Add(int a, int b) => a + b;
}
If two different services in the same request both ask for an ICalculator
, they will each get their own, separate instance.
2. Scoped
- Lifetime: A single instance is created once per client request (the "scope"). The same instance is then shared across all services that request it within that single request.
- Registration:
builder.Services.AddScoped<IMyService, MyService>();
When to use it: Scoped is the most commonly used lifetime in web applications. It's the perfect choice for services that need to maintain state within a single request, but that state should be isolated between different requests.
Example Use Case: An Entity Framework Core DbContext
. You want all the different repositories and services that are involved in a single API request to share the same DbContext
instance so they can participate in a single unit of work or transaction. However, you absolutely do not want different requests (e.g., from different users) to share the same DbContext
instance.
// In Program.cs
builder.Services.AddDbContext<MyDbContext>(options => ...);
// AddDbContext registers the context with a Scoped lifetime by default.
3. Singleton
- Lifetime: A single instance is created only once, the first time it is requested. That same instance is then used for the entire lifetime of the application, across all requests.
- Registration:
builder.Services.AddSingleton<IMyService, MyService>();
When to use it: Singleton should be used for services that are expensive to create, are thread-safe, and need to share a global state.
Example Use Case:
- A caching service (like
IMemoryCache
) where you want all parts of your application to share the same cache. - A configuration object that is loaded once at startup and then read by many different services.
- A logging service.
Important Warning: Be very careful when injecting a Scoped or Transient service into a Singleton service. This is known as a captive dependency. The Singleton service will hold onto the Scoped/Transient service for its entire lifetime, effectively promoting it to a Singleton, which can lead to unexpected and hard-to-debug bugs. The DI container will warn you about this in development.
Summary Table
Lifetime | When is a new instance created? | Use Case Example |
---|---|---|
Transient | Every time it is requested. | Lightweight, stateless services (e.g., a calculator). |
Scoped | Once per client request (the scope). | Services that share state within a request (e.g., DbContext ). |
Singleton | Only once, for the application's lifetime. | Global, shared services (e.g., a caching service). |
Conclusion
Choosing the correct service lifetime is a fundamental skill for any .NET developer. By understanding the difference between Transient, Scoped, and Singleton, you can ensure that your application is both correct and efficient. As a rule of thumb:
- Start with Scoped for most services in a web application.
- Use Transient for simple, stateless services.
- Use Singleton with care for services that are explicitly designed to be shared globally and are thread-safe.