Avoid some redundant list allocations#4963
Avoid some redundant list allocations#4963SimonCropp wants to merge 11 commits intothomhurst:mainfrom
Conversation
Code ReviewOverall this is a clean, well-targeted performance improvement. All 18 files follow the same pattern consistently, every downstream consumer correctly accepts Checked for: bugs (including multiple enumeration, deferred execution side effects, thread safety), CLAUDE.md compliance (Performance First, AOT compatibility, dual-mode requirements, snapshot testing rules). Minor Observation: Double Enumeration in
|
There was a problem hiding this comment.
Follow-up Review
The concern raised in my previous review (double enumeration of pending in AspireFixture.cs) has been addressed — that file is no longer in the PR diff. Good.
Current State
All 17 remaining changes are correct. Every .ToList() / .ToArray() that was removed is consumed exactly once downstream, so deferred evaluation introduces no multiple-enumeration risk. Specific checks:
MigrationAnalyzer.cs/XUnitTwoPhaseAnalyzer.cs—_originalRoot/OriginalRootis treated as read-only throughout each method; thecurrentRootvariable is what evolves. No aliasing issue.DisposableFieldPropertyAnalyzer.cs— eachsyntaxNodesis consumed in exactly oneforeach. Removing.ToArray()is safe even thoughGetSyntax()is slightly expensive; re-execution is never triggered.SingleTUnitAttributeAnalyzer.cs—GroupByinternalises the source immediately, so the upstreamsingleAttributesbeing lazy is inconsequential.SyntaxFactory.List(convertedStatements)— acceptsIEnumerable<T>and enumerates once internally.
One Subtle Note (not a blocker)
In CodeGenerationHelpers.cs, the elements select lambda has side effects:
var elements = typedConstant.Values.Select((v, i) =>
{
syntaxIndex++; // side effect
elementIndex++; // side effect
return TypedConstantParser.GetRawTypedConstantValue(v, paramType);
}); // was .ToList(), now lazy
argStrings.AddRange(elements);This is safe today because List.AddRange enumerates exactly once. However, the original .ToList() here was doing double duty — eliminating allocation and signalling "evaluate now because we have stateful side effects". The lazy version is slightly more fragile if someone later adds a second use of elements. A comment, or keeping this single .ToList(), would make the intent clearer. That said, it is not a correctness issue in the current code.
Summary
All changes are beneficial and correct. The regression identified in the first review was fixed. This is a clean, minimal cleanup that delivers real (if small) allocation savings across the analyzer and code-fixer hot paths.
There was a problem hiding this comment.
Follow-up Review
The concern raised in my previous review (double enumeration of pending in AspireFixture.cs) has been addressed — that file is no longer in the PR diff. Good.
Current State
All 17 remaining changes are correct. Every .ToList() / .ToArray() that was removed is consumed exactly once downstream, so deferred evaluation introduces no multiple-enumeration risk. Specific checks:
MigrationAnalyzer.cs/XUnitTwoPhaseAnalyzer.cs—_originalRoot/OriginalRootis treated as read-only throughout each method; thecurrentRootvariable is what evolves. No aliasing issue.DisposableFieldPropertyAnalyzer.cs— eachsyntaxNodesis consumed in exactly oneforeach. Removing.ToArray()is safe even thoughGetSyntax()is slightly expensive; re-execution is never triggered.SingleTUnitAttributeAnalyzer.cs—GroupByinternalises the source immediately, so the upstreamsingleAttributesbeing lazy is inconsequential.SyntaxFactory.List(convertedStatements)— acceptsIEnumerable<T>and enumerates once internally.
One Subtle Note (not a blocker)
In CodeGenerationHelpers.cs, the elements select lambda has side effects:
var elements = typedConstant.Values.Select((v, i) =>
{
syntaxIndex++; // side effect
elementIndex++; // side effect
return TypedConstantParser.GetRawTypedConstantValue(v, paramType);
}); // was .ToList(), now lazy
argStrings.AddRange(elements);This is safe today because List.AddRange enumerates exactly once. However, the original .ToList() here was doing double duty — eliminating allocation and signalling "evaluate now because we have stateful side effects". The lazy version is slightly more fragile if someone later adds a second use of elements. A comment, or keeping this single .ToList(), would make the intent clearer. That said, it is not a correctness issue in the current code.
Summary
All changes are beneficial and correct. The regression identified in the first review was fixed. This is a clean, minimal cleanup that delivers real (if small) allocation savings across the analyzer and code-fixer hot paths.
…t-list-allocations
No description provided.