Skip to content

[Efficiency Improver] perf: eliminate state-machine allocations in PropertyBag.Single(T)() and SingleOrDefault(T)()#7857

Merged
Evangelink merged 1 commit into
mainfrom
efficiency/propertybag-single-no-alloc-474709edfa199a08
Apr 27, 2026
Merged

[Efficiency Improver] perf: eliminate state-machine allocations in PropertyBag.Single(T)() and SingleOrDefault(T)()#7857
Evangelink merged 1 commit into
mainfrom
efficiency/propertybag-single-no-alloc-474709edfa199a08

Conversation

@Evangelink
Copy link
Copy Markdown
Member

@Evangelink Evangelink commented Apr 27, 2026

Replace triple-LINQ enumeration in Single() and yield-iterator enumeration in SingleOrDefault() with direct linked-list walks.

Single() previously called Any(), Skip(1).Any(), and First() on the same IEnumerable returned by Property.OfType() — allocating up to four separate yield-state-machine objects per call.

SingleOrDefault() previously called Property.OfType() and then GetEnumerator() on the result — allocating one state-machine object per call.

Both methods now walk the Property linked list directly (the same pattern already used by Property.Any() and Property.Contains()), allocating zero heap objects in the common case.

Fixes #7854

…and SingleOrDefault<T>()

Replace triple-LINQ enumeration in Single<T>() and yield-iterator
enumeration in SingleOrDefault<T>() with direct linked-list walks.

Single<T>() previously called Any(), Skip(1).Any(), and First() on the
same IEnumerable returned by Property.OfType<T>() — allocating up to
four separate yield-state-machine objects per call.

SingleOrDefault<T>() previously called Property.OfType<T>() and then
GetEnumerator() on the result — allocating one state-machine object
per call.

Both methods now walk the Property linked list directly (the same
pattern already used by Property.Any<T>() and Property.Contains()),
allocating zero heap objects in the common case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 27, 2026 07:53
@Evangelink Evangelink enabled auto-merge (squash) April 27, 2026 07:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes PropertyBag.Single<TProperty>() and PropertyBag.SingleOrDefault<TProperty>() in Microsoft.Testing.Platform by removing LINQ/yield-based enumeration paths that were creating iterator state-machine allocations, replacing them with direct linked-list traversal over the internal Property nodes.

Changes:

  • Replaced SingleOrDefault<TProperty>()’s OfType<TProperty>() + enumerator usage with a direct linked-list walk that tracks a single match and throws on duplicates.
  • Replaced Single<TProperty>()’s multi-enumeration LINQ pattern (Any(), Skip(1).Any(), First()) with a single-pass linked-list walk.
  • Added explanatory comments describing the allocation avoidance rationale.
Show a summary per file
File Description
src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs Removes iterator/LINQ-based enumeration from Single* APIs and replaces it with allocation-free linked-list traversal.

Copilot's findings

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

Comment thread src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs
@JanKrivanek
Copy link
Copy Markdown
Member

Is this on a hot path? In what scenarios and how often, with how big dataset is this being called?

We have a tradeoff of code brevity + expressiveness Versus perf - let's make sure we prefer perf if it really has impact

Copy link
Copy Markdown
Member

@JanKrivanek JanKrivanek left a comment

Choose a reason for hiding this comment

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

Approving - but please validate this is a change on hot path, with real perf impact

@Evangelink Evangelink merged commit 921d7bb into main Apr 27, 2026
49 checks passed
@Evangelink Evangelink deleted the efficiency/propertybag-single-no-alloc-474709edfa199a08 branch April 27, 2026 08:35
Sign up for free to 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: eliminate state-machine allocations in PropertyBag.Single(T)() and SingleOrDefault(T)()

3 participants