System.InvalidOperationException: A circular dependency was detected for the service of type 'X'.
ID: dotnet/di-circular-dependency
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 8 | active | — | — | — |
Root Cause
The built-in .NET DI container detects circular dependencies at resolution time when service A requires service B, and service B (directly or transitively) requires service A. This commonly occurs in layered architectures where services grow bidirectional dependencies, or when event/notification handlers reference the services that trigger them. Fix by breaking the cycle with Lazy<T>, refactoring to introduce a mediator or interface segregation, or extracting shared logic into a third service.
genericWorkarounds
-
85% success Inject Lazy<T> to defer resolution of one side of the cycle
Register a Lazy<T> factory for one of the circular services: builder.Services.AddScoped<Lazy<IServiceB>>(sp => new Lazy<IServiceB>(() => sp.GetRequiredService<IServiceB>())); Then inject Lazy<IServiceB> instead of IServiceB in ServiceA's constructor. The actual resolution is deferred until .Value is accessed, which breaks the construction-time cycle. This is a tactical fix; refactoring is preferred long-term
-
90% success Extract shared logic into a third service to break the dependency cycle
Identify the shared functionality that causes both services to depend on each other. Extract it into a new service (e.g., ISharedLogic) that both ServiceA and ServiceB depend on, instead of depending on each other. This converts the circular A<->B dependency into a tree: A->C and B->C. This is the cleanest architectural solution
-
88% success Use the Mediator pattern to decouple event-driven circular dependencies
When the cycle exists because ServiceA notifies ServiceB and ServiceB calls back to ServiceA, introduce MediatR or a simple IMediator interface. ServiceA publishes an event through the mediator, and ServiceB handles it without directly referencing ServiceA: builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); Replace direct service calls with _mediator.Send(new MyCommand()) or _mediator.Publish(new MyEvent())
Dead Ends
Common approaches that don't work:
-
Switching from constructor injection to property injection to hide the cycle
85% fail
The built-in .NET DI container does not support property injection. Even with third-party containers (Autofac, etc.) that support it, property injection merely defers the cycle resolution and creates fragile initialization order dependencies where services may be used before their properties are set
-
Registering one of the services as Transient to avoid the cycle detection
95% fail
Changing the service lifetime does not affect cycle detection. The DI container analyzes the constructor dependency graph regardless of lifetime. A transient registration still requires constructing the full dependency chain, which will still be circular
-
Using IServiceProvider directly in the constructor to manually resolve the dependency
80% fail
Resolving the circular dependency from IServiceProvider inside the constructor triggers the same cycle detection because the resolution happens during construction. This is the Service Locator anti-pattern and it moves the exception from compile-time-detectable to runtime without solving the underlying design issue