Skip to content

Remove ThrowHelper from Microsoft.Extensions.Primitives#130416

Open
svick wants to merge 4 commits into
dotnet:mainfrom
svick:primitives-throwhelper
Open

Remove ThrowHelper from Microsoft.Extensions.Primitives#130416
svick wants to merge 4 commits into
dotnet:mainfrom
svick:primitives-throwhelper

Conversation

@svick

@svick svick commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Removes ThrowHelper from Microsoft.Extensions.Primitives, replacing it with the BCL argument-validation throw helpers (ArgumentNullException.ThrowIfNull, ArgumentOutOfRangeException.ThrowIf*)
and direct throw statements. On downlevel target frameworks these are provided by the
shared ExceptionPolyfills.

The new patterns are more succinct and more modern and should be just as efficient.

The perf consideration is that a method containing the throw opcode is less likely to be inlined. This means the check (e.g. if (parameter is null)) should stay on the hot path, but the actual throwing should be done in another method (like the old ThrowHelper.Throw*Exception, an internal method in the new pattern or StringSegment.Throw*). Hiding the check in yet another method (like ArgumentNullException.ThrowIfNull) shouldn't hurt, because that method should be easily inlined.

Changes

  • Deletes ThrowHelper.cs along with its ExceptionArgument / ExceptionResource enums, and removes some per-type local throw helpers (InplaceStringBuilder's ThrowValidationError).
  • Null checks now use ArgumentNullException.ThrowIfNull.
  • Range checks now use ArgumentOutOfRangeException.ThrowIf* (e.g. ThrowIfLessThan). The caller's argument name is usually captured via CallerArgumentExpression.
  • InvalidOperationException sites become direct throw new ... using the existing SR resources. These shouldn't be on the hot path where the throw opcode would made a difference.
  • ExceptionPolyfills gains ThrowIfGreaterThan / ThrowIfGreaterThanOrEqual (matching the real ArgumentOutOfRangeException API surface) so the above works on the netstandard2.0 / net462 targets, where the real BCL methods aren't available.
  • StringSegment preserves its single-comparison fast path; the obsolete InplaceStringBuilder, where performance is mostly irrelevant, is simplified to straightforward validation.

Note

This pull request description was drafted with the assistance of GitHub Copilot.

svick and others added 3 commits July 9, 2026 15:56
Replace the local ThrowArgumentOutOfRangeException helper with the BCL
ArgumentOutOfRangeException.ThrowIf* APIs. The hot-path single unsigned
comparison is kept as a guard, delegating the throw to cold out-of-line
helpers that re-check the signed value so the exception reports the actual
argument (e.g. -1) with an enriched message instead of the unsigned
wrap-around. Adds ThrowIfGreaterThan/ThrowIfGreaterThanOrEqual polyfills for
downlevel target frameworks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace manual 'if (x < 0) throw' checks with ArgumentOutOfRangeException.ThrowIf*
and remove the now-unnecessary ThrowValidationError fast-path helper. Perf is not
a concern for this obsolete type, so validation runs unconditionally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 16:17
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-primitives
See info in area-owners.md if you want to be subscribed.

Copilot AI 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

This PR removes the internal ThrowHelper pattern from Microsoft.Extensions.Primitives, replacing it with BCL argument-validation helpers (ArgumentNullException.ThrowIfNull, ArgumentOutOfRangeException.ThrowIf*) and direct throw sites, while adding downlevel support via ExceptionPolyfills.

Changes:

  • Delete ThrowHelper.cs (and its enums) and update call sites to use BCL ThrowIf* helpers / direct throws.
  • Update StringSegment to use new cold out-of-line throw helpers (keeping unsigned-comparison fast paths).
  • Extend ExceptionPolyfills with ThrowIfGreaterThan / ThrowIfGreaterThanOrEqual, and adjust tests for updated exception ParamName.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs Updates expected ArgumentOutOfRangeException.ParamName to match new throw helper behavior.
src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs Deleted legacy throw-helper and supporting enums/resources indirection.
src/libraries/Microsoft.Extensions.Primitives/src/StringValues.cs Replaces null-check ThrowHelper usage with ArgumentNullException.ThrowIfNull.
src/libraries/Microsoft.Extensions.Primitives/src/StringTokenizer.cs Replaces ThrowHelper usage with ThrowIfNull / direct ArgumentNullException.
src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs Introduces cold throw helpers to preserve inlining/fast-path checks while modernizing validation.
src/libraries/Microsoft.Extensions.Primitives/src/InplaceStringBuilder.cs Simplifies validation/throws to modern helpers and direct throws.
src/libraries/Microsoft.Extensions.Primitives/src/CompositeChangeToken.cs Replaces ThrowHelper null-check with ArgumentNullException.ThrowIfNull.
src/libraries/Microsoft.Extensions.Primitives/src/ChangeToken.cs Replaces ThrowHelper validation and InvalidOperation throws with direct throws/resources.
src/libraries/Common/src/System/ExceptionPolyfills.cs Adds downlevel polyfills for ArgumentOutOfRangeException.ThrowIfGreaterThan*.

Comment on lines +85 to 91
ArgumentNullException.ThrowIfNull(value);
ArgumentOutOfRangeException.ThrowIfLessThan(offset, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, value.Length - offset, nameof(offset));
if (Capacity - _offset < count)
{
ThrowValidationError(value, offset, count);
throw new InvalidOperationException(SR.Format(SR.Capacity_NotEnough, value.Length, Capacity - _offset));
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have added the check.

@svick

svick commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

One more thing: I think ThrowHelper could have served as a pattern to ensure that exception messages use resources. If that's something we want, I think an analyzer would be a better choice.

Copilot AI review requested due to automatic review settings July 10, 2026 09:53
@svick svick marked this pull request as ready for review July 10, 2026 09:54
@svick svick requested review from mrek-msft, rosebyte and tarekgh July 10, 2026 09:54

Copilot AI 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

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment on lines +88 to 92
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, value.Length - offset, nameof(offset));
if (Capacity - _offset < count)
{
ThrowValidationError(value, offset, count);
throw new InvalidOperationException(SR.Format(SR.Capacity_NotEnough, value.Length, Capacity - _offset));
}

@tarekgh tarekgh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants