Why it matters
Once the scope is disposed, that service may point to disposed state.
It is like taking an ice cube out of the freezer for later; by the time you need it, it has melted.
DI002
A service resolved from a tracked IServiceScope with a known scoped registration that is returned or stored beyond that scope. The tracked scope shapes are direct CreateScope()/CreateAsyncScope() calls and existing scope locals later disposed in the same executable boundary. The rule follows the direct provider aliases it can prove and works inside constructors, accessors, local functions, lambdas, and anonymous methods.
Why it matters
Once the scope is disposed, that service may point to disposed state.
It is like taking an ice cube out of the freezer for later; by the time you need it, it has melted.
Install
dotnet add package DependencyInjection.Lifetime.Analyzers --version 3.7.8
README problem example
public IMyService GetService()
{
using var scope = _scopeFactory.CreateScope();
return scope.ServiceProvider.GetRequiredService<IMyService>();
}
README better pattern
public void UseServiceNow()
{
using var scope = _scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IMyService>();
service.Execute();
}
Guardrails
The analyzer stays quiet when the service is used only inside the scope, when lifetime is singleton/transient/unknown/unregistered, or when the receiver is a fresh/local holder, local collection, local publisher, or non-collection method. It does not guess through wrapped or pre-declared scope creation, casted/provider aliases, arbitrary helper/property flows, top-level/deferred executable boundaries, runtime Type values, dynamic/non-constant keys, plural service resolution, arrays/lists/constructor/Task/yield wrappers, receiver aliases/casts/indexers, or opaque factories. Method spelling alone is not enough: user-defined resolution extensions are not treated as Microsoft DI resolution. User-defined conversions are not treated as identity-preserving; direct identity/reference casts remain supported. These deliberate silent cases are the conservative boundary recorded in docs/adversarial/DI002.md.
Repo sample extraction
Sample app warning case
public IScopedService Bad_ServiceEscapesViaReturn()
{
using var scope = _scopeFactory.CreateScope();
// DI002: Service resolved from scope escapes via 'return'
return scope.ServiceProvider.GetRequiredService<IScopedService>();
}
Sample app safe pattern
public void Good_UsedWithinScope()
{
using var scope = _scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScopedService>();
service.DoWork(); // Used within scope - OK
}
Related guides
More documentation
Nearby diagnostics