[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute() - #8103

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0
May 11, 2026
Merged

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()#8103
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Replace GetAttributes() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.

GetAttributes() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.

The new pattern is identical to GetFirstAttributeOrDefault() and
GetSingleAttributeOrDefault() in ReflectHelper, which already use
direct array iteration for the same reason.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Fixes#8040

Replace GetAttributes<RetryBaseAttribute>() yield-return iterator with
direct iteration over GetCustomAttributesCached() to eliminate one heap
allocation per test execution.
GetAttributes<T>() is a yield-return method: every call allocates a
compiler-generated state machine object (~48 bytes). GetRetryAttribute()
is called from the TestMethodInfo constructor, which is created fresh
for every test execution. For a 10,000-test suite this avoids ~480 KB
of iterator state machine allocations, reducing GC pressure on the
common path where RetryAttribute is absent.
The new pattern is identical to GetFirstAttributeOrDefault<T>() and
GetSingleAttributeOrDefault<T>() in ReflectHelper, which already use
direct array iteration for the same reason.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 12:37

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the MSTest adapter execution hot path by removing a per-test heap allocation when resolving RetryBaseAttribute on a test method, reducing GC pressure for large test suites.

Changes:

  • Replaces enumeration via ReflectHelper.GetAttributes<RetryBaseAttribute>() (yield iterator) with direct iteration over GetCustomAttributesCached() to avoid iterator state-machine allocations.
  • Preserves existing behavior: return the single RetryBaseAttribute if present, throw when multiple are present, return null when absent.
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csReworks GetRetryAttribute() to scan the cached attribute array directly instead of using a yield-based iterator.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 1

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No issues found.

Correctness ✅ — The new code is semantically equivalent to the old code. GetAttributes<T>() internally delegates to GetCustomAttributesCached() and applies the same is TAttributeType filter via a yield return loop; the new code inlines that loop directly. All three outcomes (zero → null, one → return it, two+ → throw) are preserved identically.

Threading ✅ — GetCustomAttributesCached uses a ConcurrentDictionary with GetOrAdd, so the shared cache is accessed safely. No new mutable state is introduced.

Performance ✅ — The optimization is real and correctly motivated. The old using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator() call allocates a compiler-generated state machine object on every invocation (even when no RetryBaseAttribute is present, which is the common case). The new foreach over Attribute[] avoids that allocation entirely, consistent with the approach already used by GetFirstAttributeOrDefault<T> and GetSingleAttributeOrDefault<T>.

Resources ✅ — The using on the old IEnumerator<T> was required to correctly handle finally blocks inside the yield return iterator. The new loop iterates a plain Attribute[]; arrays do not implement IDisposable and have no finally-block semantics, so no cleanup is needed.

API Compat / Cross-TFM / Security / Defensive ✅ — Private method, no public surface area change. Only basic foreach and is pattern matching used — compatible with all target frameworks.

Positive Observations

The change follows the established pattern in ReflectHelper (GetFirstAttributeOrDefault, GetSingleAttributeOrDefault) and consolidates to a single, consistent idiom for attribute iteration throughout the hot path. The PR description accurately explains both the mechanism and the magnitude of the saving.


Generated by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

  • [Coverage] No unit tests cover GetRetryAttribute() — the refactored private method called from the TestMethodInfo constructor. Three paths exist (null, single attribute, multiple attributes/throw) and none are exercised by TestMethodInfoTests.cs or any other test file. This is a pre-existing gap, but the refactor is a natural opportunity to add coverage. See the inline comment for a concrete suggestion.

Recommendations

  1. Add tests to TestMethodInfoTests.cs that create a TestMethodInfo wrapping a method with: (a) no RetryBaseAttribute, (b) one RetryBaseAttribute, (c) two RetryBaseAttribute attributes — asserting the RetryAttribute property value or expected exception respectively.

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No significant nitpicks found. This is a well-crafted, surgical performance improvement.

Highlights:

  • The new pattern is consistent with the already-established GetSingleAttributeOrDefault<T>() in ReflectHelper — same loop structure, same blank-line separation between the throw guard and the happy-path assignment.
  • ThrowMultipleAttributesException is already annotated [DoesNotReturn], so the compiler correctly understands that found = retryAttribute is only reachable on the success path.
  • The explanatory comment is well-placed and educational — it prevents future maintainers from "simplifying" back to the allocating iterator form.
  • Variable names (found, attribute, retryAttribute) are clear and idiomatic for C# pattern-matching style.

Recommendations

None required. The implementation is idiomatic, correct, and consistent with existing codebase patterns.


🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

@Evangelink
Amaury Levé (Evangelink) merged commit b7fa6c7 into mainMay 11, 2026
111 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-iterator-alloc-getretryattribute-ad4e78ae9fa642b0 branch May 11, 2026 14:26
Amaury Levé (Evangelink) added a commit that referenced this pull request May 12, 2026
…ibute() (#8103)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Efficiency Improver] perf: avoid iterator allocation in GetRetryAttribute()

2 participants

@Evangelink