Avoid some redundant list allocations#4963
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
The .ToList() removal at BaseMigrationCodeFixProvider.cs was incorrect because the result is subsequently used with .Add(), which requires a List<T>. Also merges latest main to resolve CI failures.
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.18.37 to 1.19.16. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.19.16 <!-- Release notes generated using configuration in .github/release.yml at v1.19.16 --> ## What's Changed ### Other Changes * Truncate exceptions in GitHub summary tables by @thomhurst in thomhurst/TUnit#5108 ### Dependencies * chore(deps): update tunit to 1.19.11 by @thomhurst in thomhurst/TUnit#5106 * chore(deps): bump dompurify from 3.3.0 to 3.3.2 in /docs by @dependabot[bot] in thomhurst/TUnit#5096 * chore(deps): bump svgo from 3.2.0 to 3.3.3 in /docs by @dependabot[bot] in thomhurst/TUnit#5086 **Full Changelog**: thomhurst/TUnit@v1.19.11...v1.19.16 ## 1.19.11 <!-- Release notes generated using configuration in .github/release.yml at v1.19.11 --> ## What's Changed ### Other Changes * Fix HTML report sort to also reorder groups by @thomhurst in thomhurst/TUnit#5103 * fix: correct expand-all icon SVG in HTML report by @slang25 in thomhurst/TUnit#5105 * Avoid some redundant list allocations by @SimonCropp in thomhurst/TUnit#4963 * avoid some redundant enumerable alloc by @SimonCropp in thomhurst/TUnit#4972 * use char instead of string for joins by @SimonCropp in thomhurst/TUnit#4989 ### Dependencies * chore(deps): update dependency nunit to 4.5.1 by @thomhurst in thomhurst/TUnit#5097 * chore(deps): update tunit to 1.19.0 by @thomhurst in thomhurst/TUnit#5099 * chore(deps): update dependency humanizer to 3.0.10 by @thomhurst in thomhurst/TUnit#5101 * chore(deps): update dependency nunit.analyzers to 4.12.0 by @thomhurst in thomhurst/TUnit#5102 **Full Changelog**: thomhurst/TUnit@v1.19.0...v1.19.11 ## 1.19.0 <!-- Release notes generated using configuration in .github/release.yml at v1.19.0 --> ## What's Changed ### Other Changes * fix: improve CreateTestVariant API and fix void/ValueTask return types by @thomhurst in thomhurst/TUnit#5095 ### Dependencies * chore(deps): update tunit to 1.18.37 by @thomhurst in thomhurst/TUnit#5094 **Full Changelog**: thomhurst/TUnit@v1.18.37...v1.19.0 Commits viewable in [compare view](thomhurst/TUnit@v1.18.37...v1.19.16). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
No description provided.