Compile-time DI guardrails

DependencyInjection.Lifetime.Analyzers

Catch DI scope leaks, captive dependencies, BuildServiceProvider misuse, and unresolvable services before they become runtime bugs, flaky tests, or production-only failures.

Why teams install it

  • Find captive dependencies before stale state and thread-safety bugs ship.
  • Catch scope leaks before they become ObjectDisposedException incidents.
  • Detect missing registrations and implementation mismatches before runtime activation fails.
  • Push DI rules into CI instead of relying on reviewer memory.

Latest release

2.18.24

2026-07-17

  • DI021/DI022 non-generic captured-scope resolutions — concurrent-handler analysis now recognizes exact framework IServiceProvider.GetService(typeof(T)) and GetRequiredService(typeof(T)) requests made through a scope or provider captured outside the handler. Literal typeof(T) is required, direct-static ordinal recovery is restricted to exact framework extension containers, and concrete provider implementations bind the requested type by semantic parameter type rather than parameter spelling. Provider-origin tracing preserves built-in identity, reference, and boxing conversions so existing generic/keyed resolution through captured value-type and IServiceProvider-constrained providers remains covered; constraint traversal is cycle-safe for temporarily invalid editor code. Runtime Type values, user-defined conversions on either the requested type or provider receiver, user-defined same-named helpers, and scopes created inside the handler remain silent.

Featured diagnostics

High-intent landing pages for common DI bugs

View all rules

DI003

Captive Dependency

singleton services capturing scoped or transient dependencies, including constructor injection, `IEnumerable<T>` collection captures, known scoped framework services such as `IOptionsSnapshot<T>`, EF Core contexts and `DbContextOptions<TContext>` registrations from `AddDbContext(...)`, `AddDbContextFactory(...)`, `AddDbContextPool(...)`, and `AddPooledDbContextFactory(...)` including service/implementation overload self-registrations, and high-confidence factory paths such as inline delegates, stable local delegate factories, method-group factories, `GetServices<T>()`, keyed resolutions, and `ActivatorUtilities.CreateInstance(...)` calls where DI still resolves a scoped or transient constructor parameter.

Severity: Warning · Code fix: Yes

DI015

Unresolvable Dependency

registered services with direct or transitive constructor/factory dependencies that are not registered (including keyed and open-generic paths).

Severity: Warning · Code fix: Yes

DI016

BuildServiceProvider Misuse

`BuildServiceProvider()` calls while composing registrations (for example in `ConfigureServices`, `IServiceCollection` extension registration methods, registration lambdas, or builder-style `.Services` helper flows), whether written as reduced extension syntax (`services.BuildServiceProvider()`) or as a direct static call (`ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(builder.Services)`).

Severity: Warning · Code fix: No

DI001

Service Scope Not Disposed

`IServiceScope` instances created with `CreateScope()` or `CreateAsyncScope()` that are never disposed, including scopes whose only disposal call is hidden behind a conditional branch, switch section, loop, catch block, or after a branch exit that can bypass shared cleanup. DI001 recognizes predeclared nullable scope locals assigned conditionally when a later conditional-access, non-null-guarded, same-branch pre-exit, or `finally` disposal reliably closes ownership, and it treats directly returned scopes as caller-owned even through simple casts or conditional return arms. Reassignment leaks and loop-created scopes that need per-iteration disposal still report.

Severity: Warning · Code fix: Yes

DI014

Root Service Provider Not Disposed

root providers from `BuildServiceProvider()` that are never disposed, including local providers whose only manual disposal is conditional, catch-only, after reassignment to another provider, or after repeated creation inside a loop. Straight-line explicit disposal, standard `Dispose()` to `Dispose(true)` cleanup, and caller-owned return flows are accepted even when the `BuildServiceProvider()` result is parenthesized, same-instance cast, null-forgiven, selected by a ternary arm, or supplied by a null-coalescing operand; user-defined conversions remain reportable because they may produce a different instance.

Severity: Warning · Code fix: Yes

Search-targeted pages

Common DI failure searches mapped to the right rules

See all problem guides

Problem guide

Spot Constructor Over-Injection Early

This rule is intentionally softer than runtime-failure diagnostics, but it is useful for preventing DI-heavy classes from becoming maintenance hotspots.

Problem guide

Use CreateAsyncScope In Async Methods

Use this rule when background services, async handlers, or hosted-service workflows are mixing sync scopes into async disposal paths.

Adoption

Roll it out without noise

Start with the default severities, promote high-confidence rules to errors, and use the sample-driven rule pages to explain the policy to the team.