DI002

Scoped Service Escapes Scope

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.

Warning Default severity · Code fix: Yes

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.

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();
}

Yes. The fixer offers only a pragma suppression for an intentionally accepted escape; it does not rewrite ownership or move the service. It refuses diagnostics that are not contained by a statement. Use the standard severity setting, for example:

Guardrails

When DI002 stays silent

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

Examples pulled from the sample app

Open full sample file

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
    }

Nearby diagnostics

Other rules in this family

All 37 rules