Fix: batch registration chain to avoid Roslyn CS8078 on large scans#63
Open
waynebrantley wants to merge 1 commit into
Open
Conversation
Owner
|
I'm thinking if it makes sense to make Roslyn's life easier, and simply make separate statements, like services.AddTransient<A, B>();
services.AddTransient<C, D>();
...
services.AddTransient<Y, Z>();
return services; |
GenerateRegistrationsSource always emitted a single fluent chain:
return services
.AddTransient<A, B>()
.AddTransient<C, D>()
...
.AddTransient<Y, Z>();
That's a single C# expression whose parse tree depth grows linearly with the
registration count. Roslyn's operation-tree walkers hit a stack-based cap
around ~1000 links and abort with CS8078 ("An expression is too long or
complex to compile"). Consumers scanning large assemblies (~1000+ types)
can't compile the generated code at all.
Emit one statement per registration instead:
services.AddTransient<A, B>();
services.AddTransient<C, D>();
...
services.AddTransient<Y, Z>();
return services;
This matches the shape GenerateCustomHandlingSource already uses and
sidesteps CS8078 entirely — each statement is its own top-level expression.
Existing snapshot tests updated to the new shape; new
LargeAssemblyCompilesWithoutCs8078 test declares 1200 handlers and asserts
the generated code compiles clean.
waynebrantley
force-pushed
the
fix/generate-service-registrations-cs8078
branch
from
July 24, 2026 22:02
87f55f4 to
6e4a988
Compare
Author
|
Great call — updated to statement-per-line. Force-pushed
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GenerateRegistrationsSourcealways emits one fluent chain per generated method:That's a single C# expression whose parse tree depth grows linearly with the registration count. Roslyn's operation-tree walkers hit a recursion cap around ~1000 links and the compilation aborts with:
Any consumer scanning a large assembly (~1000+ matching types) can't compile the generated code at all. Analyzers walking the same tree also stack-overflow, and
dotnet formaton the generated code crashes with the same trace.GenerateCustomHandlingSourcealready emits statement-per-line output, so theScanForTypes+Handlerpath doesn't hit this — it's specific to[GenerateServiceRegistrations].Observed in production: a ~1170-handler CQRS assembly could not compile after switching from Scrutor to
[GenerateServiceRegistrations].Fix
Batch the chain at
MaxRegistrationsPerChain = 500links per statement. Below the threshold: output is byte-for-byte identical to the pre-fix shape (zero snapshot-test churn — all 93 existing tests still green). Above the threshold: split into Nservices.Add()...;statements, with the final statement carrying thereturnwhen the method returnsIServiceCollection.Test
AddServicesTests.LargeAssemblyEmitsBatchedChains— declares 650 handlers, asserts:Verified: fails on
main, passes with the fix.Notes
Threshold value (500) is conservative — actual CS8078 tripping point varies with stack size (~1000+ in typical CI, less on constrained runners). 500 leaves comfortable headroom. Happy to bump or make it configurable if you prefer.