From 7ba39d50d8d15b55442fd0e17ec15e700b7b831b Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 19 Sep 2026 09:17:10 +1000 Subject: [PATCH] Add ArrayBufferWriter polyfill Recreates System.Buffers.ArrayBufferWriter for the targets where the BCL does not have it (net461-net481, netstandard2.0, netcoreapp2.0-2.2, uap10.0), gated on FeatureMemory since it needs IBufferWriter, Memory and Span. Everything from netcoreapp3.0/netstandard2.1 up gets a TypeForwardedTo. The implementation was taken from the net11 System.Memory assembly, so the growth formula, the Array.MaxLength clamp and the exception types and messages match the shipping type. ResetWrittenCount only arrived in net8.0, so netcoreapp3.x, netstandard2.1 and net5.0-net7.0 have the type but not that member. As with CollectionsMarshal, that window is covered by a separate extension method file. It delegates to Clear(), which zeroes the reclaimed space and is O(n) rather than O(1), noted in the api list. Neither System.Memory nor Microsoft.Bcl.Memory ship the type, so no RefsBclMemory guard is needed. Closes #610 --- apiCount.include.md | 38 +-- api_list.include.md | 8 + assemblySize.include.md | 72 +++--- readme.md | 122 ++++----- src/ApiBuilderTests/BuildApiTest.cs | 1 + src/Consume/Consume.cs | 21 ++ src/Polyfill/ArrayBufferWriter.cs | 210 ++++++++++++++++ src/Polyfill/Polyfill_ArrayBufferWriter.cs | 21 ++ src/Split/net10.0/TypeForwardeds.cs | 1 + src/Split/net11.0/TypeForwardeds.cs | 1 + src/Split/net461/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net462/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net47/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net471/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net472/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net48/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/net481/ArrayBufferWriter.cs | 153 +++++++++++ .../net5.0/Polyfill_ArrayBufferWriter.cs | 12 + src/Split/net5.0/TypeForwardeds.cs | 1 + .../net6.0/Polyfill_ArrayBufferWriter.cs | 12 + src/Split/net6.0/TypeForwardeds.cs | 1 + .../net7.0/Polyfill_ArrayBufferWriter.cs | 12 + src/Split/net7.0/TypeForwardeds.cs | 1 + src/Split/net8.0/TypeForwardeds.cs | 1 + src/Split/net9.0/TypeForwardeds.cs | 1 + src/Split/netcoreapp2.0/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/netcoreapp2.1/ArrayBufferWriter.cs | 153 +++++++++++ src/Split/netcoreapp2.2/ArrayBufferWriter.cs | 153 +++++++++++ .../Polyfill_ArrayBufferWriter.cs | 12 + src/Split/netcoreapp3.0/TypeForwardeds.cs | 1 + .../Polyfill_ArrayBufferWriter.cs | 12 + src/Split/netcoreapp3.1/TypeForwardeds.cs | 1 + src/Split/netstandard2.0/ArrayBufferWriter.cs | 153 +++++++++++ .../Polyfill_ArrayBufferWriter.cs | 12 + src/Split/netstandard2.1/TypeForwardeds.cs | 1 + src/Split/uap10.0/ArrayBufferWriter.cs | 153 +++++++++++ src/Tests/PolyfillTests_ArrayBufferWriter.cs | 238 ++++++++++++++++++ 37 files changed, 2537 insertions(+), 112 deletions(-) create mode 100644 src/Polyfill/ArrayBufferWriter.cs create mode 100644 src/Polyfill/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/net461/ArrayBufferWriter.cs create mode 100644 src/Split/net462/ArrayBufferWriter.cs create mode 100644 src/Split/net47/ArrayBufferWriter.cs create mode 100644 src/Split/net471/ArrayBufferWriter.cs create mode 100644 src/Split/net472/ArrayBufferWriter.cs create mode 100644 src/Split/net48/ArrayBufferWriter.cs create mode 100644 src/Split/net481/ArrayBufferWriter.cs create mode 100644 src/Split/net5.0/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/net6.0/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/net7.0/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/netcoreapp2.0/ArrayBufferWriter.cs create mode 100644 src/Split/netcoreapp2.1/ArrayBufferWriter.cs create mode 100644 src/Split/netcoreapp2.2/ArrayBufferWriter.cs create mode 100644 src/Split/netcoreapp3.0/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/netcoreapp3.1/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/netstandard2.0/ArrayBufferWriter.cs create mode 100644 src/Split/netstandard2.1/Polyfill_ArrayBufferWriter.cs create mode 100644 src/Split/uap10.0/ArrayBufferWriter.cs create mode 100644 src/Tests/PolyfillTests_ArrayBufferWriter.cs diff --git a/apiCount.include.md b/apiCount.include.md index 76746650..a95bf691 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -1,28 +1,28 @@ -**API count: 1180** +**API count: 1182** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 1084 | -| `net462` | 1084 | -| `net47` | 1083 | -| `net471` | 1082 | -| `net472` | 1078 | -| `net48` | 1078 | -| `net481` | 1078 | -| `netstandard2.0` | 1080 | -| `netstandard2.1` | 913 | -| `netcoreapp2.0` | 1003 | -| `netcoreapp2.1` | 926 | -| `netcoreapp2.2` | 926 | -| `netcoreapp3.0` | 887 | -| `netcoreapp3.1` | 886 | -| `net5.0` | 764 | -| `net6.0` | 660 | -| `net7.0` | 521 | +| `net461` | 1085 | +| `net462` | 1085 | +| `net47` | 1084 | +| `net471` | 1083 | +| `net472` | 1079 | +| `net48` | 1079 | +| `net481` | 1079 | +| `netstandard2.0` | 1081 | +| `netstandard2.1` | 914 | +| `netcoreapp2.0` | 1004 | +| `netcoreapp2.1` | 927 | +| `netcoreapp2.2` | 927 | +| `netcoreapp3.0` | 888 | +| `netcoreapp3.1` | 887 | +| `net5.0` | 765 | +| `net6.0` | 661 | +| `net7.0` | 522 | | `net8.0` | 400 | | `net9.0` | 253 | | `net10.0` | 190 | | `net11.0` | 58 | -| `uap10.0` | 1070 | +| `uap10.0` | 1071 | diff --git a/api_list.include.md b/api_list.include.md index 7d8959d1..f8bb02f0 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -38,6 +38,12 @@ * `void ThrowIfZero(T) where T : struct, IEquatable` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifzero?view=net-11.0#system-argumentoutofrangeexception-throwifzero-1(-0-system-string)) +#### ArrayBufferWriter + + * `void ResetWrittenCount()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.resetwrittencount?view=net-11.0) + * Note: Delegates to Clear(), so the written region is zeroed: O(n) where the BCL is O(1), and the reclaimed space is zeroed instead of holding the stale data the BCL leaves behind. + + #### ArraySegment * `void CopyTo(ArraySegment)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.arraysegment-1.copyto?view=net-11.0#system-arraysegment-1-copyto(-0())) @@ -1820,3 +1826,5 @@ #### ReadOnlySequenceStream +#### ArrayBufferWriter + diff --git a/assemblySize.include.md b/assemblySize.include.md index c056a923..2a39b6b8 100644 --- a/assemblySize.include.md +++ b/assemblySize.include.md @@ -2,24 +2,24 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 388.5KB | +380.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 344.5KB | +336.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net461 | 8.5KB | 387.0KB | +378.5KB | +9.5KB | +6.5KB | +9.0KB | +14.0KB | -| net462 | 7.0KB | 391.0KB | +384.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 390.5KB | +383.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 389.5KB | +381.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net472 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net48 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net481 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 366.5KB | +357.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 348.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp2.2 | 9.0KB | 348.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.0 | 9.5KB | 346.5KB | +337.0KB | +9.5KB | +7.0KB | +9.5KB | +14.0KB | -| netcoreapp3.1 | 9.5KB | 345.0KB | +335.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net5.0 | 9.5KB | 310.5KB | +301.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net6.0 | 10.0KB | 251.5KB | +241.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | -| net7.0 | 10.0KB | 221.0KB | +211.0KB | +9.0KB | +5.5KB | +512bytes | +3.5KB | -| net8.0 | 9.5KB | 191.5KB | +182.0KB | +8.5KB | | +512bytes | +3.0KB | +| netstandard2.0 | 8.0KB | 390.5KB | +382.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netstandard2.1 | 8.5KB | 345.0KB | +336.5KB | +8.5KB | +6.5KB | +8.5KB | +13.5KB | +| net461 | 8.5KB | 389.0KB | +380.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net462 | 7.0KB | 392.5KB | +385.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net47 | 7.0KB | 392.5KB | +385.5KB | +8.5KB | +6.5KB | +9.0KB | +14.0KB | +| net471 | 8.5KB | 391.5KB | +383.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net472 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.5KB | +| net48 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net481 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.5KB | +| netcoreapp2.0 | 9.0KB | 368.0KB | +359.0KB | +9.0KB | +7.0KB | +9.5KB | +14.0KB | +| netcoreapp2.1 | 9.0KB | 350.5KB | +341.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp2.2 | 9.0KB | 350.5KB | +341.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp3.0 | 9.5KB | 347.0KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp3.1 | 9.5KB | 345.0KB | +335.5KB | +9.5KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 310.5KB | +301.0KB | +9.0KB | +7.0KB | +9.5KB | +14.0KB | +| net6.0 | 10.0KB | 252.0KB | +242.0KB | +9.5KB | +6.5KB | +512bytes | +3.0KB | +| net7.0 | 10.0KB | 221.0KB | +211.0KB | +9.5KB | +6.0KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 191.5KB | +182.0KB | +8.5KB | | +512bytes | +3.5KB | | net9.0 | 9.5KB | 120.0KB | +110.5KB | +8.5KB | | +512bytes | +3.5KB | | net10.0 | 10.0KB | 95.5KB | +85.5KB | +8.5KB | | +512bytes | +3.5KB | | net11.0 | 10.0KB | 21.0KB | +11.0KB | +9.0KB | | +512bytes | +3.5KB | @@ -29,24 +29,24 @@ | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 565.4KB | +557.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 494.4KB | +485.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net461 | 8.5KB | 564.9KB | +556.4KB | +17.2KB | +8.2KB | +13.9KB | +19.4KB | -| net462 | 7.0KB | 568.9KB | +561.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 568.2KB | +561.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 566.8KB | +558.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net472 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net48 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net481 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 532.8KB | +523.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 502.6KB | +493.6KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp2.2 | 9.0KB | 502.6KB | +493.6KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.0 | 9.5KB | 494.0KB | +484.5KB | +17.2KB | +8.7KB | +14.4KB | +19.4KB | -| netcoreapp3.1 | 9.5KB | 492.4KB | +482.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net5.0 | 9.5KB | 440.5KB | +431.0KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net6.0 | 10.0KB | 361.3KB | +351.3KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | -| net7.0 | 10.0KB | 314.3KB | +304.3KB | +16.6KB | +6.9KB | +1.1KB | +4.2KB | -| net8.0 | 9.5KB | 271.0KB | +261.5KB | +16.0KB | +299bytes | +1.1KB | +3.7KB | +| netstandard2.0 | 8.0KB | 568.8KB | +560.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netstandard2.1 | 8.5KB | 495.2KB | +486.7KB | +16.2KB | +8.2KB | +13.4KB | +18.9KB | +| net461 | 8.5KB | 568.4KB | +559.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net462 | 7.0KB | 571.9KB | +564.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net47 | 7.0KB | 571.6KB | +564.6KB | +16.2KB | +8.2KB | +13.9KB | +19.4KB | +| net471 | 8.5KB | 570.3KB | +561.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net472 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.9KB | +| net48 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net481 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.9KB | +| netcoreapp2.0 | 9.0KB | 535.8KB | +526.8KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| netcoreapp2.1 | 9.0KB | 506.1KB | +497.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp2.2 | 9.0KB | 506.1KB | +497.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp3.0 | 9.5KB | 494.7KB | +485.2KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp3.1 | 9.5KB | 492.7KB | +483.2KB | +17.2KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 440.7KB | +431.2KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| net6.0 | 10.0KB | 362.0KB | +352.0KB | +17.2KB | +8.2KB | +1.1KB | +3.7KB | +| net7.0 | 10.0KB | 314.6KB | +304.6KB | +17.1KB | +7.4KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 271.1KB | +261.6KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | | net9.0 | 9.5KB | 173.6KB | +164.1KB | +16.0KB | | +1.1KB | +4.2KB | | net10.0 | 10.0KB | 139.1KB | +129.1KB | +16.0KB | | +1.1KB | +4.2KB | | net11.0 | 10.0KB | 30.9KB | +20.9KB | +16.5KB | | +1.1KB | +4.2KB | diff --git a/readme.md b/readme.md index a348bd98..f06cea7f 100644 --- a/readme.md +++ b/readme.md @@ -13,34 +13,34 @@ The package targets `netstandard2.0` and is designed to support the following ru * `uap10` -**API count: 1180** +**API count: 1182** ### Per Target Framework | Target | APIs | | -- | -- | -| `net461` | 1084 | -| `net462` | 1084 | -| `net47` | 1083 | -| `net471` | 1082 | -| `net472` | 1078 | -| `net48` | 1078 | -| `net481` | 1078 | -| `netstandard2.0` | 1080 | -| `netstandard2.1` | 913 | -| `netcoreapp2.0` | 1003 | -| `netcoreapp2.1` | 926 | -| `netcoreapp2.2` | 926 | -| `netcoreapp3.0` | 887 | -| `netcoreapp3.1` | 886 | -| `net5.0` | 764 | -| `net6.0` | 660 | -| `net7.0` | 521 | +| `net461` | 1085 | +| `net462` | 1085 | +| `net47` | 1084 | +| `net471` | 1083 | +| `net472` | 1079 | +| `net48` | 1079 | +| `net481` | 1079 | +| `netstandard2.0` | 1081 | +| `netstandard2.1` | 914 | +| `netcoreapp2.0` | 1004 | +| `netcoreapp2.1` | 927 | +| `netcoreapp2.2` | 927 | +| `netcoreapp3.0` | 888 | +| `netcoreapp3.1` | 887 | +| `net5.0` | 765 | +| `net6.0` | 661 | +| `net7.0` | 522 | | `net8.0` | 400 | | `net9.0` | 253 | | `net10.0` | 190 | | `net11.0` | 58 | -| `uap10.0` | 1070 | +| `uap10.0` | 1071 | @@ -96,24 +96,24 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 388.5KB | +380.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netstandard2.1 | 8.5KB | 344.5KB | +336.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net461 | 8.5KB | 387.0KB | +378.5KB | +9.5KB | +6.5KB | +9.0KB | +14.0KB | -| net462 | 7.0KB | 391.0KB | +384.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 390.5KB | +383.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 389.5KB | +381.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net472 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net48 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net481 | 8.5KB | 388.5KB | +380.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.0 | 9.0KB | 366.5KB | +357.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 348.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp2.2 | 9.0KB | 348.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.0 | 9.5KB | 346.5KB | +337.0KB | +9.5KB | +7.0KB | +9.5KB | +14.0KB | -| netcoreapp3.1 | 9.5KB | 345.0KB | +335.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net5.0 | 9.5KB | 310.5KB | +301.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| net6.0 | 10.0KB | 251.5KB | +241.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | -| net7.0 | 10.0KB | 221.0KB | +211.0KB | +9.0KB | +5.5KB | +512bytes | +3.5KB | -| net8.0 | 9.5KB | 191.5KB | +182.0KB | +8.5KB | | +512bytes | +3.0KB | +| netstandard2.0 | 8.0KB | 390.5KB | +382.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netstandard2.1 | 8.5KB | 345.0KB | +336.5KB | +8.5KB | +6.5KB | +8.5KB | +13.5KB | +| net461 | 8.5KB | 389.0KB | +380.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net462 | 7.0KB | 392.5KB | +385.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net47 | 7.0KB | 392.5KB | +385.5KB | +8.5KB | +6.5KB | +9.0KB | +14.0KB | +| net471 | 8.5KB | 391.5KB | +383.0KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net472 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.5KB | +| net48 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB | +| net481 | 8.5KB | 390.0KB | +381.5KB | +9.0KB | +6.5KB | +9.5KB | +14.5KB | +| netcoreapp2.0 | 9.0KB | 368.0KB | +359.0KB | +9.0KB | +7.0KB | +9.5KB | +14.0KB | +| netcoreapp2.1 | 9.0KB | 350.5KB | +341.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp2.2 | 9.0KB | 350.5KB | +341.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp3.0 | 9.5KB | 347.0KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | +| netcoreapp3.1 | 9.5KB | 345.0KB | +335.5KB | +9.5KB | +6.5KB | +9.5KB | +14.0KB | +| net5.0 | 9.5KB | 310.5KB | +301.0KB | +9.0KB | +7.0KB | +9.5KB | +14.0KB | +| net6.0 | 10.0KB | 252.0KB | +242.0KB | +9.5KB | +6.5KB | +512bytes | +3.0KB | +| net7.0 | 10.0KB | 221.0KB | +211.0KB | +9.5KB | +6.0KB | +512bytes | +3.5KB | +| net8.0 | 9.5KB | 191.5KB | +182.0KB | +8.5KB | | +512bytes | +3.5KB | | net9.0 | 9.5KB | 120.0KB | +110.5KB | +8.5KB | | +512bytes | +3.5KB | | net10.0 | 10.0KB | 95.5KB | +85.5KB | +8.5KB | | +512bytes | +3.5KB | | net11.0 | 10.0KB | 21.0KB | +11.0KB | +9.0KB | | +512bytes | +3.5KB | @@ -123,24 +123,24 @@ This project uses features from the newest stable SDK and C# language. As such c | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 565.4KB | +557.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netstandard2.1 | 8.5KB | 494.4KB | +485.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net461 | 8.5KB | 564.9KB | +556.4KB | +17.2KB | +8.2KB | +13.9KB | +19.4KB | -| net462 | 7.0KB | 568.9KB | +561.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 568.2KB | +561.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 566.8KB | +558.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net472 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net48 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net481 | 8.5KB | 564.7KB | +556.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.0 | 9.0KB | 532.8KB | +523.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 502.6KB | +493.6KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp2.2 | 9.0KB | 502.6KB | +493.6KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.0 | 9.5KB | 494.0KB | +484.5KB | +17.2KB | +8.7KB | +14.4KB | +19.4KB | -| netcoreapp3.1 | 9.5KB | 492.4KB | +482.9KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net5.0 | 9.5KB | 440.5KB | +431.0KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| net6.0 | 10.0KB | 361.3KB | +351.3KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | -| net7.0 | 10.0KB | 314.3KB | +304.3KB | +16.6KB | +6.9KB | +1.1KB | +4.2KB | -| net8.0 | 9.5KB | 271.0KB | +261.5KB | +16.0KB | +299bytes | +1.1KB | +3.7KB | +| netstandard2.0 | 8.0KB | 568.8KB | +560.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netstandard2.1 | 8.5KB | 495.2KB | +486.7KB | +16.2KB | +8.2KB | +13.4KB | +18.9KB | +| net461 | 8.5KB | 568.4KB | +559.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net462 | 7.0KB | 571.9KB | +564.9KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net47 | 7.0KB | 571.6KB | +564.6KB | +16.2KB | +8.2KB | +13.9KB | +19.4KB | +| net471 | 8.5KB | 570.3KB | +561.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net472 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.9KB | +| net48 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB | +| net481 | 8.5KB | 567.7KB | +559.2KB | +16.7KB | +8.2KB | +14.4KB | +19.9KB | +| netcoreapp2.0 | 9.0KB | 535.8KB | +526.8KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| netcoreapp2.1 | 9.0KB | 506.1KB | +497.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp2.2 | 9.0KB | 506.1KB | +497.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp3.0 | 9.5KB | 494.7KB | +485.2KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | +| netcoreapp3.1 | 9.5KB | 492.7KB | +483.2KB | +17.2KB | +8.2KB | +14.4KB | +19.4KB | +| net5.0 | 9.5KB | 440.7KB | +431.2KB | +16.7KB | +8.7KB | +14.4KB | +19.4KB | +| net6.0 | 10.0KB | 362.0KB | +352.0KB | +17.2KB | +8.2KB | +1.1KB | +3.7KB | +| net7.0 | 10.0KB | 314.6KB | +304.6KB | +17.1KB | +7.4KB | +1.1KB | +4.2KB | +| net8.0 | 9.5KB | 271.1KB | +261.6KB | +16.0KB | +299bytes | +1.1KB | +4.2KB | | net9.0 | 9.5KB | 173.6KB | +164.1KB | +16.0KB | | +1.1KB | +4.2KB | | net10.0 | 10.0KB | 139.1KB | +129.1KB | +16.0KB | | +1.1KB | +4.2KB | | net11.0 | 10.0KB | 30.9KB | +20.9KB | +16.5KB | | +1.1KB | +4.2KB | @@ -705,6 +705,12 @@ The class `Polyfill` includes the following extension methods: * `void ThrowIfZero(T) where T : struct, IEquatable` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception.throwifzero?view=net-11.0#system-argumentoutofrangeexception-throwifzero-1(-0-system-string)) +#### ArrayBufferWriter + + * `void ResetWrittenCount()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.resetwrittencount?view=net-11.0) + * Note: Delegates to Clear(), so the written region is zeroed: O(n) where the BCL is O(1), and the reclaimed space is zeroed instead of holding the stale data the BCL leaves behind. + + #### ArraySegment * `void CopyTo(ArraySegment)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.arraysegment-1.copyto?view=net-11.0#system-arraysegment-1-copyto(-0())) @@ -2486,6 +2492,8 @@ The class `Polyfill` includes the following extension methods: #### WritableMemoryStream #### ReadOnlySequenceStream + +#### ArrayBufferWriter @@ -2684,7 +2692,7 @@ void ObjectDisposedExceptionExample(bool isDisposed) ObjectDisposedException.ThrowIf(isDisposed, typeof(Consume)); } ``` -snippet source | anchor +snippet source | anchor @@ -2703,7 +2711,7 @@ void EnsureExample(Order order, Customer customer, string customerId, string ema this.quantity = Ensure.NotNegativeOrZero(quantity); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/ApiBuilderTests/BuildApiTest.cs b/src/ApiBuilderTests/BuildApiTest.cs index 62268d20..7da2cb20 100644 --- a/src/ApiBuilderTests/BuildApiTest.cs +++ b/src/ApiBuilderTests/BuildApiTest.cs @@ -37,6 +37,7 @@ public Task RunWithRoslyn() WriteType("ReadOnlyMemoryStream", writer, ref count); WriteType("WritableMemoryStream", writer, ref count); WriteType("ReadOnlySequenceStream", writer, ref count); + WriteType("ArrayBufferWriter", writer, ref count); count += Directory.EnumerateFiles(polyfillDir, "*Attribute.cs", SearchOption.AllDirectories).Count(); // Index and Range diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index 5557bdfb..8c0e413f 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -349,6 +349,27 @@ void StreamWrappersUsage() sequenceStream.Read(bytes, 0, bytes.Length); } } + + void ArrayBufferWriterUsage() + { + var defaultCapacity = new System.Buffers.ArrayBufferWriter(); + System.Buffers.IBufferWriter bufferWriter = defaultCapacity; + + var writer = new System.Buffers.ArrayBufferWriter(16); + Span span = writer.GetSpan(4); + span[0] = 1; + writer.Advance(1); + Memory memory = writer.GetMemory(4); + memory.Span[0] = 2; + writer.Advance(1); + ReadOnlyMemory writtenMemory = writer.WrittenMemory; + ReadOnlySpan writtenSpan = writer.WrittenSpan; + int writtenCount = writer.WrittenCount; + int capacity = writer.Capacity; + int freeCapacity = writer.FreeCapacity; + writer.ResetWrittenCount(); + writer.Clear(); + } #endif #if FeatureMemory && !RefsBclMemory diff --git a/src/Polyfill/ArrayBufferWriter.cs b/src/Polyfill/ArrayBufferWriter.cs new file mode 100644 index 00000000..da39b647 --- /dev/null +++ b/src/Polyfill/ArrayBufferWriter.cs @@ -0,0 +1,210 @@ +#nullable enable + +// ArrayBufferWriter ships in the BCL from netcoreapp3.0/netstandard2.1. This polyfill covers the +// earlier targets; where the runtime provides the type it is forwarded at the end of this file. +// ResetWrittenCount only arrived in net8.0, so for the targets that have the type but not that +// member it is added as an extension method in Polyfill_ArrayBufferWriter.cs. +#if FeatureMemory + +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +#pragma warning disable + +namespace System.Buffers; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +//Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1?view=net-11.0 +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + // Array.MaxLength is not exposed on the targets this type is compiled for. + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + + T[] buffer; + int index; + + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.-ctor?view=net-11.0#system-buffers-arraybufferwriter-1-ctor + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.-ctor?view=net-11.0#system-buffers-arraybufferwriter-1-ctor(system-int32) + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + + buffer = new T[initialCapacity]; + index = 0; + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.writtenmemory?view=net-11.0 + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.writtenspan?view=net-11.0 + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.writtencount?view=net-11.0 + public int WrittenCount => index; + + /// + /// Gets the total amount of space within the underlying buffer. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.capacity?view=net-11.0 + public int Capacity => buffer.Length; + + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.freecapacity?view=net-11.0 + public int FreeCapacity => buffer.Length - index; + + /// + /// Clears the data written to the underlying buffer. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.clear?view=net-11.0 + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.resetwrittencount?view=net-11.0 + public void ResetWrittenCount() => + index = 0; + + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.advance?view=net-11.0 + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + + index += count; + } + + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.getmemory?view=net-11.0 + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.getspan?view=net-11.0 + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + + if (sizeHint == 0) + { + sizeHint = 1; + } + + if (sizeHint <= FreeCapacity) + { + return; + } + + var currentLength = buffer.Length; + + // Attempt to grow by the larger of the sizeHint and double the current size. + var growBy = Math.Max(sizeHint, currentLength); + + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + + var newSize = currentLength + growBy; + + if ((uint) newSize > ArrayMaxLength) + { + // Attempt to grow to the maximum array length instead. + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + + newSize = ArrayMaxLength; + } + + Array.Resize(ref buffer, newSize); + } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] +#endif + +#endif diff --git a/src/Polyfill/Polyfill_ArrayBufferWriter.cs b/src/Polyfill/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..16ee2afb --- /dev/null +++ b/src/Polyfill/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,21 @@ +// ResetWrittenCount was added to the BCL ArrayBufferWriter in net8.0, but the type itself +// exists from netcoreapp3.0/netstandard2.1. For those targets the type is present (so it cannot +// be recreated) and ResetWrittenCount is added as an extension method. For earlier targets it +// lives on the recreated type in ArrayBufferWriter.cs. +#if (NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER) && !NET8_0_OR_GREATER + +namespace Polyfills; + +using System.Buffers; + +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.arraybufferwriter-1.resetwrittencount?view=net-11.0 + //Note: Delegates to Clear(), so the written region is zeroed: O(n) where the BCL is O(1), and the reclaimed space is zeroed instead of holding the stale data the BCL leaves behind. + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} +#endif diff --git a/src/Split/net10.0/TypeForwardeds.cs b/src/Split/net10.0/TypeForwardeds.cs index cd4dd06b..e642b9fa 100644 --- a/src/Split/net10.0/TypeForwardeds.cs +++ b/src/Split/net10.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] diff --git a/src/Split/net11.0/TypeForwardeds.cs b/src/Split/net11.0/TypeForwardeds.cs index 3b219ba6..53683001 100644 --- a/src/Split/net11.0/TypeForwardeds.cs +++ b/src/Split/net11.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] diff --git a/src/Split/net461/ArrayBufferWriter.cs b/src/Split/net461/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net461/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net462/ArrayBufferWriter.cs b/src/Split/net462/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net462/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net47/ArrayBufferWriter.cs b/src/Split/net47/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net47/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net471/ArrayBufferWriter.cs b/src/Split/net471/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net471/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net472/ArrayBufferWriter.cs b/src/Split/net472/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net472/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net48/ArrayBufferWriter.cs b/src/Split/net48/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net48/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net481/ArrayBufferWriter.cs b/src/Split/net481/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/net481/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/net5.0/Polyfill_ArrayBufferWriter.cs b/src/Split/net5.0/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/net5.0/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/net5.0/TypeForwardeds.cs b/src/Split/net5.0/TypeForwardeds.cs index eafd698b..987169ec 100644 --- a/src/Split/net5.0/TypeForwardeds.cs +++ b/src/Split/net5.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] diff --git a/src/Split/net6.0/Polyfill_ArrayBufferWriter.cs b/src/Split/net6.0/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/net6.0/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/net6.0/TypeForwardeds.cs b/src/Split/net6.0/TypeForwardeds.cs index 37a8e7d7..070368c6 100644 --- a/src/Split/net6.0/TypeForwardeds.cs +++ b/src/Split/net6.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] diff --git a/src/Split/net7.0/Polyfill_ArrayBufferWriter.cs b/src/Split/net7.0/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/net7.0/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/net7.0/TypeForwardeds.cs b/src/Split/net7.0/TypeForwardeds.cs index efb09098..f201c2b7 100644 --- a/src/Split/net7.0/TypeForwardeds.cs +++ b/src/Split/net7.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] diff --git a/src/Split/net8.0/TypeForwardeds.cs b/src/Split/net8.0/TypeForwardeds.cs index f90cc3d6..97624dc0 100644 --- a/src/Split/net8.0/TypeForwardeds.cs +++ b/src/Split/net8.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CollectionBuilderAttribute))] diff --git a/src/Split/net9.0/TypeForwardeds.cs b/src/Split/net9.0/TypeForwardeds.cs index 002bf769..a52dd01e 100644 --- a/src/Split/net9.0/TypeForwardeds.cs +++ b/src/Split/net9.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Buffers.Text.Base64Url))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] diff --git a/src/Split/netcoreapp2.0/ArrayBufferWriter.cs b/src/Split/netcoreapp2.0/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/netcoreapp2.0/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/netcoreapp2.1/ArrayBufferWriter.cs b/src/Split/netcoreapp2.1/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/netcoreapp2.1/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/netcoreapp2.2/ArrayBufferWriter.cs b/src/Split/netcoreapp2.2/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/netcoreapp2.2/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/netcoreapp3.0/Polyfill_ArrayBufferWriter.cs b/src/Split/netcoreapp3.0/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/netcoreapp3.0/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/netcoreapp3.0/TypeForwardeds.cs b/src/Split/netcoreapp3.0/TypeForwardeds.cs index 21404082..f04022dd 100644 --- a/src/Split/netcoreapp3.0/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.0/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] diff --git a/src/Split/netcoreapp3.1/Polyfill_ArrayBufferWriter.cs b/src/Split/netcoreapp3.1/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/netcoreapp3.1/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/netcoreapp3.1/TypeForwardeds.cs b/src/Split/netcoreapp3.1/TypeForwardeds.cs index 21404082..f04022dd 100644 --- a/src/Split/netcoreapp3.1/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Numerics.BitOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] diff --git a/src/Split/netstandard2.0/ArrayBufferWriter.cs b/src/Split/netstandard2.0/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/netstandard2.0/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Split/netstandard2.1/Polyfill_ArrayBufferWriter.cs b/src/Split/netstandard2.1/Polyfill_ArrayBufferWriter.cs new file mode 100644 index 00000000..2f72b87f --- /dev/null +++ b/src/Split/netstandard2.1/Polyfill_ArrayBufferWriter.cs @@ -0,0 +1,12 @@ +// +#pragma warning disable +namespace Polyfills; +using System.Buffers; +static partial class Polyfill +{ + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public static void ResetWrittenCount(this ArrayBufferWriter target) => + target.Clear(); +} diff --git a/src/Split/netstandard2.1/TypeForwardeds.cs b/src/Split/netstandard2.1/TypeForwardeds.cs index 2c58215a..812517ea 100644 --- a/src/Split/netstandard2.1/TypeForwardeds.cs +++ b/src/Split/netstandard2.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))] [assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/uap10.0/ArrayBufferWriter.cs b/src/Split/uap10.0/ArrayBufferWriter.cs new file mode 100644 index 00000000..93602448 --- /dev/null +++ b/src/Split/uap10.0/ArrayBufferWriter.cs @@ -0,0 +1,153 @@ +// +#pragma warning disable +#nullable enable +#if FeatureMemory +#pragma warning disable +namespace System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +sealed class ArrayBufferWriter : + IBufferWriter +{ + const int ArrayMaxLength = 0x7FFFFFC7; + const int DefaultInitialBufferSize = 256; + T[] buffer; + int index; + /// + /// Initializes a new instance of the class with the default initial capacity. + /// + public ArrayBufferWriter() + { + buffer = Array.Empty(); + index = 0; + } + /// + /// Initializes a new instance of the class with the specified initial capacity. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// is less than or equal to 0. + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + { + throw new ArgumentException(null, nameof(initialCapacity)); + } + buffer = new T[initialCapacity]; + index = 0; + } + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, index); + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); + /// + /// Gets the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => index; + /// + /// Gets the total amount of space within the underlying buffer. + /// + public int Capacity => buffer.Length; + /// + /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => buffer.Length - index; + /// + /// Clears the data written to the underlying buffer. + /// + public void Clear() + { + buffer.AsSpan(0, index).Clear(); + index = 0; + } + /// + /// Resets the data written to the underlying buffer without zeroing its contents. + /// + public void ResetWrittenCount() => + index = 0; + /// + /// Notifies the that data items were written to the output or . + /// + /// The number of data items written. + /// is negative. + /// The method call attempts to advance past the end of the underlying buffer. + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentException(null, nameof(count)); + } + if (index > buffer.Length - count) + { + throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}."); + } + index += count; + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsMemory(index); + } + /// + /// Returns a to write to that is at least the requested size, as specified by . + /// + /// The minimum length of the returned . If 0, a non-empty buffer is returned. + /// is negative. + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + return buffer.AsSpan(index); + } + void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + { + throw new ArgumentException(null, nameof(sizeHint)); + } + if (sizeHint == 0) + { + sizeHint = 1; + } + if (sizeHint <= FreeCapacity) + { + return; + } + var currentLength = buffer.Length; + var growBy = Math.Max(sizeHint, currentLength); + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + var newSize = currentLength + growBy; + if ((uint) newSize > ArrayMaxLength) + { + var needed = (uint) (currentLength - FreeCapacity + sizeHint); + if (needed > ArrayMaxLength) + { + throw new OutOfMemoryException($"Cannot allocate a buffer of size {needed}."); + } + newSize = ArrayMaxLength; + } + Array.Resize(ref buffer, newSize); + } +} +#endif diff --git a/src/Tests/PolyfillTests_ArrayBufferWriter.cs b/src/Tests/PolyfillTests_ArrayBufferWriter.cs new file mode 100644 index 00000000..299e7e06 --- /dev/null +++ b/src/Tests/PolyfillTests_ArrayBufferWriter.cs @@ -0,0 +1,238 @@ +#if FeatureMemory +using System.Buffers; + +partial class PolyfillTests +{ + [Test] + public async Task ArrayBufferWriter_DefaultCapacity() + { + var writer = new ArrayBufferWriter(); + + await Assert.That(writer.WrittenCount).IsEqualTo(0); + await Assert.That(writer.Capacity).IsEqualTo(0); + await Assert.That(writer.FreeCapacity).IsEqualTo(0); + await Assert.That(writer.WrittenMemory.Length).IsEqualTo(0); + } + + [Test] + public async Task ArrayBufferWriter_InitialCapacity() + { + var writer = new ArrayBufferWriter(16); + + await Assert.That(writer.WrittenCount).IsEqualTo(0); + await Assert.That(writer.Capacity).IsEqualTo(16); + await Assert.That(writer.FreeCapacity).IsEqualTo(16); + } + + [Test] + public async Task ArrayBufferWriter_InvalidInitialCapacity() + { + await Assert.That(() => + { + var _ = new ArrayBufferWriter(0); + }).Throws(); + + await Assert.That(() => + { + var _ = new ArrayBufferWriter(-1); + }).Throws(); + } + + [Test] + public async Task ArrayBufferWriter_WriteThroughSpan() + { + var writer = new ArrayBufferWriter(8); + + var span = writer.GetSpan(3); + var available = span.Length; + span[0] = 1; + span[1] = 2; + span[2] = 3; + writer.Advance(3); + + var written = writer.WrittenSpan.ToArray(); + + await Assert.That(available).IsGreaterThanOrEqualTo(3); + await Assert.That(written).IsEquivalentTo(new byte[] {1, 2, 3}); + await Assert.That(writer.WrittenCount).IsEqualTo(3); + await Assert.That(writer.FreeCapacity).IsEqualTo(writer.Capacity - 3); + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new byte[] {1, 2, 3}); + } + + [Test] + public async Task ArrayBufferWriter_WriteThroughMemory() + { + var writer = new ArrayBufferWriter(8); + + var memory = writer.GetMemory(2); + memory.Span[0] = 10; + memory.Span[1] = 20; + writer.Advance(2); + + await Assert.That(memory.Length).IsGreaterThanOrEqualTo(2); + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new byte[] {10, 20}); + } + + [Test] + public async Task ArrayBufferWriter_WritesInMultipleChunks() + { + var writer = new ArrayBufferWriter(2); + + for (var i = 0; i < 10; i++) + { + var span = writer.GetSpan(1); + span[0] = i; + writer.Advance(1); + } + + await Assert.That(writer.WrittenCount).IsEqualTo(10); + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + } + + [Test] + public async Task ArrayBufferWriter_GrowsFromEmpty() + { + var writer = new ArrayBufferWriter(); + + var length = writer.GetSpan().Length; + + // An empty writer grows to at least the default initial buffer size of 256. + await Assert.That(length).IsGreaterThanOrEqualTo(256); + await Assert.That(writer.Capacity).IsGreaterThanOrEqualTo(256); + } + + [Test] + public async Task ArrayBufferWriter_GrowsToHonorSizeHint() + { + var writer = new ArrayBufferWriter(4); + + var length = writer.GetSpan(1000).Length; + + await Assert.That(length).IsGreaterThanOrEqualTo(1000); + await Assert.That(writer.Capacity).IsGreaterThanOrEqualTo(1000); + } + + [Test] + public async Task ArrayBufferWriter_GetSpanNeverEmpty() + { + var writer = new ArrayBufferWriter(1); + writer.GetSpan()[0] = 1; + writer.Advance(1); + + // Free capacity is exhausted, so even a zero size hint has to grow the buffer. + var length = writer.GetSpan().Length; + + await Assert.That(length).IsGreaterThan(0); + } + + [Test] + public async Task ArrayBufferWriter_AdvanceNegative() + { + var writer = new ArrayBufferWriter(4); + + await Assert.That(() => writer.Advance(-1)).Throws(); + } + + [Test] + public async Task ArrayBufferWriter_AdvancePastEnd() + { + var writer = new ArrayBufferWriter(4); + + await Assert.That(() => writer.Advance(writer.Capacity + 1)).Throws(); + } + + [Test] + public async Task ArrayBufferWriter_NegativeSizeHint() + { + var writer = new ArrayBufferWriter(4); + + await Assert.That(() => + { + writer.GetSpan(-1); + }).Throws(); + + await Assert.That(() => + { + writer.GetMemory(-1); + }).Throws(); + } + + [Test] + public async Task ArrayBufferWriter_Clear() + { + var writer = new ArrayBufferWriter(8); + var span = writer.GetSpan(3); + span[0] = 1; + span[1] = 2; + span[2] = 3; + writer.Advance(3); + var capacity = writer.Capacity; + + writer.Clear(); + var reread = writer.GetSpan(3).Slice(0, 3).ToArray(); + + await Assert.That(writer.WrittenCount).IsEqualTo(0); + await Assert.That(writer.Capacity).IsEqualTo(capacity); + await Assert.That(writer.FreeCapacity).IsEqualTo(capacity); + // Clear zeroes the written region, unlike ResetWrittenCount. + await Assert.That(reread).IsEquivalentTo(new byte[] {0, 0, 0}); + } + + [Test] + public async Task ArrayBufferWriter_ResetWrittenCount() + { + var writer = new ArrayBufferWriter(8); + var span = writer.GetSpan(3); + span[0] = 1; + span[1] = 2; + span[2] = 3; + writer.Advance(3); + var capacity = writer.Capacity; + + writer.ResetWrittenCount(); + + await Assert.That(writer.WrittenCount).IsEqualTo(0); + await Assert.That(writer.Capacity).IsEqualTo(capacity); + await Assert.That(writer.FreeCapacity).IsEqualTo(capacity); + await Assert.That(writer.WrittenMemory.Length).IsEqualTo(0); + } + + [Test] + public async Task ArrayBufferWriter_ReusableAfterReset() + { + var writer = new ArrayBufferWriter(8); + writer.GetSpan(1)[0] = 1; + writer.Advance(1); + + writer.ResetWrittenCount(); + writer.GetSpan(1)[0] = 9; + writer.Advance(1); + + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new byte[] {9}); + } + + [Test] + public async Task ArrayBufferWriter_AsIBufferWriter() + { + var writer = new ArrayBufferWriter(8); + IBufferWriter bufferWriter = writer; + + bufferWriter.GetSpan(2)[0] = 7; + bufferWriter.Advance(1); + + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new byte[] {7}); + } + + [Test] + public async Task ArrayBufferWriter_ReferenceTypeElements() + { + var writer = new ArrayBufferWriter(2); + var span = writer.GetSpan(2); + span[0] = "one"; + span[1] = "two"; + writer.Advance(2); + + await Assert.That(writer.WrittenMemory.ToArray()).IsEquivalentTo(new[] {"one", "two"}); + } +} +#endif