DI009

Open Generic Captive Dependency

open generic singleton registrations that depend on shorter-lived services.

Default severity: Warning · Code fix: Yes

Why it matters

every closed generic instance inherits the lifetime mismatch.

If the recipe is wrong at the top of the cookbook, every dish made from it comes out wrong.

README problem example

services.AddScoped<IScopedService, ScopedService>();
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));

public sealed class Repository<T> : IRepository<T>
{
    public Repository(IScopedService scoped) { }
}

README better pattern

services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

Yes. Can adjust lifetime for open generic registrations.

Repo sample extraction

Examples pulled from the sample app

Open full sample file

Sample app warning case

public class BadRepository<T> : IBadRepository<T>
{
    private readonly IScopedService _scopedService;

    // DI009 will be reported on the registration line for this constructor
    public BadRepository(IScopedService scopedService)
    {
        _scopedService = scopedService;
    }

    public T? Get(int id)
    {
#pragma warning disable DI007 // Intentional use of service locator in sample
        _scopedService.DoWork();
#pragma warning restore DI007
        return default;
    }

    public void Save(T entity)
    {
#pragma warning disable DI007 // Intentional use of service locator in sample
        _scopedService.DoWork();
#pragma warning restore DI007
    }
}

Sample app safe pattern

public class GoodScopedRepository<T> : IGoodScopedRepository<T>
{
    private readonly IScopedService _scopedService;

    public GoodScopedRepository(IScopedService scopedService)
    {
        _scopedService = scopedService;
    }

    public T? Get(int id)
    {
#pragma warning disable DI007 // Intentional use of service locator in sample
        _scopedService.DoWork();
#pragma warning restore DI007
        return default;
    }
}