Skip to content

perf: replace LINQ in Flatten/AllSucceeded/AnyFailed with for-loops#116

Merged
Chris-Wolfgang merged 2 commits into
mainfrom
perf/flatten-allsucceeded-anyfailed
May 11, 2026
Merged

perf: replace LINQ in Flatten/AllSucceeded/AnyFailed with for-loops#116
Chris-Wolfgang merged 2 commits into
mainfrom
perf/flatten-allsucceeded-anyfailed

Conversation

@Chris-Wolfgang
Copy link
Copy Markdown
Owner

Summary

  • AllSucceeded / AnyFailed: replace LINQ All / Any with index-based for loops. Eliminates lambda allocation + iterator state machine on the hot path.
  • Flatten: single-pass scan with short-circuits on the common cases:
    • all-success: no allocation, returns cached Success() singleton (once perf: cache Result.Success() singleton #114 lands)
    • single-failure: returns the original ErrorMessage with no string.Join or StringBuilder allocation
    • multiple failures: lazily allocates a StringBuilder
  • Drop now-unused using System.Linq;.

Behavior unchanged.

Test plan

  • dotnet build -c Release — 0 warnings, 0 errors
  • dotnet test -c Release --framework net8.0 — 97/97 passing
  • CI green across all TFMs
  • Verify benchmark deltas locally if desired

🤖 Generated with Claude Code

- AllSucceeded / AnyFailed: replace `results.All(...)` / `results.Any(...)`
  with index-based for-loops. Eliminates the allocated lambda + iterator
  state machine on the hot path.

- Flatten: single-pass scan that short-circuits on the common cases:
  - all-success: no allocation, return cached Success() singleton
  - single-failure: return the original ErrorMessage with no string.Join
    or StringBuilder allocation
  - multiple failures: lazily allocate a StringBuilder
  Previously LINQ allocated a Where iterator + Select iterator + lambda
  closures + invoked string.Join even when there were zero failures.

Behavior unchanged. All 97 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 11, 2026 16:31
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 core Result aggregation helpers in the Try-Pattern library by removing LINQ from hot paths and replacing it with allocation-free index-based loops, while preserving the existing success/failure semantics and error message formatting.

Changes:

  • Replaced LINQ-based implementations of AnyFailed and AllSucceeded with for loops to avoid iterator/lambda allocations.
  • Reworked Result.Flatten into a single-pass scan that short-circuits the common cases (all success / single failure) and only allocates a StringBuilder when multiple failures exist.
  • Removed the now-unused System.Linq import and added System.Text for StringBuilder.

Comment thread src/Wolfgang.TryPattern/Result.cs
Comment thread src/Wolfgang.TryPattern/Result.cs
Comment thread src/Wolfgang.TryPattern/Result.cs
@Chris-Wolfgang Chris-Wolfgang merged commit b82dfdd into main May 11, 2026
8 checks passed
@Chris-Wolfgang Chris-Wolfgang deleted the perf/flatten-allsucceeded-anyfailed branch May 11, 2026 21:43
@Chris-Wolfgang Chris-Wolfgang mentioned this pull request May 18, 2026
4 tasks
Chris-Wolfgang added a commit that referenced this pull request May 19, 2026
Changes since v0.3.1:

- fix: null-element validation in Result.Flatten / AllSucceeded /
  AnyFailed (#113) — previously calling `.Failed` on a null element
  threw NullReferenceException; now throws ArgumentException with the
  index of the offending element. Adds [NotNull] post-condition
  attribute to all three methods plus all Try.Run/RunAsync overloads.
  Polyfills NotNullAttribute for net462 / netstandard2.0.
- perf: Result.Success() now returns a cached singleton instead of
  allocating per call (#114). Behavior is locked in by a test using
  Assert.Same; XML docs warn callers not to rely on reference identity.
- perf: replace LINQ in Flatten / AllSucceeded / AnyFailed with index-
  based for-loops (#116). Flatten is now single-pass with a lazy
  StringBuilder so the common single-failure case has zero extra
  allocation. AllSucceeded / AnyFailed short-circuit on first
  failure, so a trailing null past the decisive element no longer
  throws (documented in <exception> XML and locked in by tests).
- refactor: simplify Result ctor validation (#115) — switch-when
  replaced with two guard-clause ifs. No behavior change; exception
  messages and ParamName preserved.
- chore: test cleanup (#117) — remove redundant inheritance-
  verification tests, fix tabs vs spaces, drop unused pragma.
- chore(deps): Bump Meziantou.Analyzer 3.0.77 → 3.0.85 (#118).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants