.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.