Skip to content

Add the BitConverter span overloads for netstandard2.0 and .NET Framework - #599

Merged
SimonCropp merged 1 commit into
mainfrom
bitconverter-span-netstandard
Sep 10, 2026
Merged

Add the BitConverter span overloads for netstandard2.0 and .NET Framework#599
SimonCropp merged 1 commit into
mainfrom
bitconverter-span-netstandard

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Follow-up to #598, which flagged these while diffing the type. Twenty members: the ten To*(ReadOnlySpan<byte>) reads and the ten matching TryWriteBytes(Span<byte>, T) writes, for bool, char, short, int, long, float, double, ushort, uint and ulong.

Availability confirmed against the reference assemblies

target has them
net461 – net481 no
netstandard2.0 no
netcoreapp2.0 no
netstandard2.1, netcoreapp2.1+ yes

So the guard is !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER, which is the pattern ConvertPolyfill, GuidPolyfill, EncodingPolyfill and others already use. This is the oldest-target end of the range, where these are arguably most useful — it is the group that has no span-based conversion at all today.

Implementation

Reads and writes go through MemoryMarshal.Read<T> / MemoryMarshal.Write<T>, which is exactly what BinaryPrimitivesPolyfill already does under the same FeatureMemory constraint. That reads unaligned, which matters: span offsets are arbitrary, and the tests deliberately read at offsets 1, 3 and 5 so an implementation assuming alignment would fault or misread. It also avoids copying, so these are not the "correct but allocates" compromise — no //Note: needed on any of the twenty.

ToBoolean is the one done by hand, as value[0] != 0. Reinterpreting the byte as a bool via MemoryMarshal would produce a non-canonical bool for inputs like 2, which is not something to create deliberately even though it happens to test as true.

Semantics verified against net11

  • A short span is rejected with a plain ArgumentOutOfRangeException naming valuenot the ArgumentException the array overloads throw for the equivalent "index valid but too few bytes remain" case. The two families genuinely differ here, and Add net9 BitConverter Int128 and UInt128 members #598 matched the array behaviour for the same type, so getting this the other way round would have been an easy mistake.
  • TryWriteBytes leaves the destination untouched when it refuses, rather than partially writing.
  • A larger destination is written only up to the width of the value, leaving trailing bytes alone. Asserted by filling with 0xAA and checking the tail survives.
  • ToBoolean treats any non-zero byte as true, matching the array overload.

Tests

Four tests, running on every target framework, so netstandard2.1/netcoreapp2.1 and later check the BCL and net462 checks the polyfill — all four pass on both sides. Reads are cross-checked against the corresponding array overload at each offset rather than against expected constants, which is an oracle the polyfill has no part in.

Result

API count 1119 → 1139.

Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1710), net10.0 (1710), net9.0 (1710), net8.0 (1707), net462 (1665), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests.

…work

Twenty members: ToBoolean, ToChar, ToInt16, ToInt32, ToInt64, ToSingle, ToDouble,
ToUInt16, ToUInt32 and ToUInt64 over a ReadOnlySpan, and the matching TryWriteBytes.
Confirmed against the reference assemblies to be missing on net461 through net481,
netstandard2.0 and netcoreapp2.0, and present from netstandard2.1 and netcoreapp2.1,
so the guard is the pattern already used elsewhere in the repo.

Reads and writes go through MemoryMarshal, as BinaryPrimitivesPolyfill already does,
which reads unaligned and does not copy. ToBoolean is done by hand rather than
reinterpreting the byte, since any non-zero byte is true and a non-canonical bool is
not something to create deliberately.

Verified against net11: a short span is rejected with a plain ArgumentOutOfRangeException
naming value, not the ArgumentException the array overloads use for the same shape;
TryWriteBytes leaves the destination untouched when it refuses; and a larger destination
is written only up to the width of the value.

The tests read at odd offsets, which would fault or misread under an implementation that
assumed alignment, and run on every target, so netstandard2.1 and later check the BCL
while net462 checks the polyfill.

API count 1119 -> 1139.
@SimonCropp SimonCropp added this to the 11.3.0 milestone Sep 10, 2026
@SimonCropp
SimonCropp merged commit 9ad8f45 into main Sep 10, 2026
4 of 6 checks passed
@SimonCropp
SimonCropp deleted the bitconverter-span-netstandard branch September 10, 2026 06:14
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
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.

1 participant