📖 Business
Object Lifetime Management
Object lifetime management determines how long a dependency lives once created and how many consumers share the same instance. Van Deursen and Seemann identify three core lifestyles: Transient (new instance every time), Scoped (one instance per logical operation like an HTTP request), and Singleton (one instance for the application's entire lifetime). Choosing the wrong lifestyle doesn't cause compile-time errors — it causes subtle runtime bugs, most dangerously the "captive dependency" problem where a long-lived object holds a reference to a short-lived dependency, keeping it alive past its intended scope. Understanding lifetimes is the difference between a DI configuration that works and one that silently corrupts data under load.
2
Minutes
2
Concepts
+45
XP
1
How It Works
- Transient lifestyle — A new instance is created every time the dependency is requested. This is the simplest and safest default — no shared state, no threading concerns. The cost is object allocation overhead, which is negligible for most services but can matter for expensive-to-create objects like database connections.
- Scoped lifestyle — One instance is created per logical scope, typically an HTTP request or a unit of work. All classes within that scope share the same instance, enabling patterns like sharing a database context across multiple repositories within a single request. The scope is explicitly created and disposed, ensuring cleanup of resources like database connections.
- Singleton lifestyle — One instance exists for the application's entire lifetime. All consumers across all threads and requests share the same object. This demands thread safety and is appropriate only for stateless services or carefully synchronized shared resources.
- Captive dependency bug — The most critical lifetime mistake: a Singleton service depends on a Scoped or Transient service, "capturing" it and extending its lifetime indefinitely. Example: a Singleton
OrderServiceinjected with a ScopedDbContext— the DbContext outlives its intended request scope, accumulating stale entities and eventually corrupting data or exhausting connections.
Lifetime compatibility (safe → unsafe):
Singleton → Singleton ✅
Scoped → Scoped or Singleton ✅
Transient → Transient, Scoped, or Singleton ✅
Singleton → Scoped ❌ CAPTIVE DEPENDENCY
Singleton → Transient ❌ CAPTIVE DEPENDENCY
Scoped → Transient ⚠️ (often okay, but the Transient lives longer than expected)
- Lifestyle mismatch detection — Some containers (Simple Injector) detect captive dependencies at configuration time and throw diagnostic warnings. Microsoft.Extensions.DependencyInjection does this only when
ValidateScopesis enabled (default in Development). Always enable scope validation in development environments.