DI017

Circular Dependency

constructor-injection cycles such as `A -> B -> A`, including longer transitive loops. It follows effective registration precedence, including exact closed registrations before open-generic fallbacks, and mirrors the default container's constructor-set rule: the greediest resolvable constructor is analyzed only when its resolved service identifiers (type plus key) contain every other resolvable constructor's service identifiers. Equivalent reordered constructors therefore expose the same real cycle, while non-superset sets stay silent because activation is ambiguous.

Default severity: Warning · Code fix: No

Why it matters

the default DI container cannot resolve circular constructor graphs and will fail at runtime when the service is activated.

If two people each wait for the other to hand over the key first, the door never opens.

README problem example

services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IPaymentService, PaymentService>();

public sealed class OrderService : IOrderService
{
    public OrderService(IPaymentService payment) { }
}

public sealed class PaymentService : IPaymentService
{
    public PaymentService(IOrderService order) { }
}

README better pattern

No. Breaking dependency cycles is a design change.

Repo sample extraction

Examples pulled from the sample app

Open full sample file

Sample app circular dependency

public class BadOrderService : IOrderService
{
    public BadOrderService(IPaymentService payment) { }
}

Sample app safe pattern

public class GoodOrderService : IOrderService { }

Related guides

  • No problem-guide pages point here yet.