diff --git a/apiCount.include.md b/apiCount.include.md
index 200d7720..bf6bbc26 100644
--- a/apiCount.include.md
+++ b/apiCount.include.md
@@ -1,28 +1,28 @@
-**API count: 1016**
+**API count: 1020**
### Per Target Framework
| Target | APIs |
| -- | -- |
-| `net461` | 963 |
-| `net462` | 963 |
-| `net47` | 962 |
-| `net471` | 961 |
-| `net472` | 957 |
-| `net48` | 957 |
-| `net481` | 957 |
-| `netstandard2.0` | 959 |
-| `netstandard2.1` | 811 |
-| `netcoreapp2.0` | 880 |
-| `netcoreapp2.1` | 822 |
-| `netcoreapp2.2` | 822 |
-| `netcoreapp3.0` | 769 |
-| `netcoreapp3.1` | 768 |
-| `net5.0` | 640 |
-| `net6.0` | 545 |
-| `net7.0` | 392 |
-| `net8.0` | 273 |
-| `net9.0` | 179 |
-| `net10.0` | 126 |
-| `net11.0` | 58 |
-| `uap10.0` | 949 |
+| `net461` | 967 |
+| `net462` | 967 |
+| `net47` | 966 |
+| `net471` | 965 |
+| `net472` | 961 |
+| `net48` | 961 |
+| `net481` | 961 |
+| `netstandard2.0` | 963 |
+| `netstandard2.1` | 815 |
+| `netcoreapp2.0` | 884 |
+| `netcoreapp2.1` | 826 |
+| `netcoreapp2.2` | 826 |
+| `netcoreapp3.0` | 773 |
+| `netcoreapp3.1` | 772 |
+| `net5.0` | 644 |
+| `net6.0` | 549 |
+| `net7.0` | 396 |
+| `net8.0` | 277 |
+| `net9.0` | 183 |
+| `net10.0` | 130 |
+| `net11.0` | 62 |
+| `uap10.0` | 953 |
diff --git a/api_list.include.md b/api_list.include.md
index a1d1ab73..ec671003 100644
--- a/api_list.include.md
+++ b/api_list.include.md
@@ -1505,3 +1505,11 @@
#### UnreachableException
+#### StringStream
+
+#### ReadOnlyMemoryStream
+
+#### WritableMemoryStream
+
+#### ReadOnlySequenceStream
+
diff --git a/assemblySize.include.md b/assemblySize.include.md
index 8bab779b..27bcf822 100644
--- a/assemblySize.include.md
+++ b/assemblySize.include.md
@@ -2,51 +2,51 @@
| | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability |
|----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------|
-| netstandard2.0 | 8.0KB | 342.5KB | +334.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netstandard2.1 | 8.5KB | 297.0KB | +288.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net461 | 8.5KB | 341.0KB | +332.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net462 | 7.0KB | 345.0KB | +338.0KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB |
-| net47 | 7.0KB | 344.5KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net471 | 8.5KB | 343.5KB | +335.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net472 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB |
-| net48 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB |
-| net481 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.0 | 9.0KB | 318.5KB | +309.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.1 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.2 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp3.0 | 9.5KB | 292.0KB | +282.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp3.1 | 9.5KB | 290.0KB | +280.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
-| net5.0 | 9.5KB | 254.0KB | +244.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
-| net6.0 | 10.0KB | 195.0KB | +185.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB |
-| net7.0 | 10.0KB | 157.5KB | +147.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB |
-| net8.0 | 9.5KB | 129.0KB | +119.5KB | +8.5KB | | +512bytes | +3.5KB |
-| net9.0 | 9.5KB | 82.5KB | +73.0KB | +8.5KB | | +512bytes | +3.5KB |
-| net10.0 | 10.0KB | 60.0KB | +50.0KB | +9.0KB | | +512bytes | +3.5KB |
-| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB |
+| netstandard2.0 | 8.0KB | 347.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| netstandard2.1 | 8.5KB | 302.0KB | +293.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| net461 | 8.5KB | 346.5KB | +338.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net462 | 7.0KB | 350.0KB | +343.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net47 | 7.0KB | 349.5KB | +342.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
+| net471 | 8.5KB | 349.0KB | +340.5KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB |
+| net472 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net48 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net481 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.0 | 9.0KB | 323.5KB | +314.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.1 | 9.0KB | 304.5KB | +295.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.2 | 9.0KB | 304.5KB | +295.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp3.0 | 9.5KB | 297.0KB | +287.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| netcoreapp3.1 | 9.5KB | 295.5KB | +286.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net5.0 | 9.5KB | 259.0KB | +249.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
+| net6.0 | 10.0KB | 201.0KB | +191.0KB | +10.0KB | +6.5KB | +512bytes | +3.5KB |
+| net7.0 | 10.0KB | 163.5KB | +153.5KB | +9.0KB | +5.5KB | +512bytes | +3.0KB |
+| net8.0 | 9.5KB | 135.0KB | +125.5KB | +8.0KB | | +512bytes | +3.0KB |
+| net9.0 | 9.5KB | 88.5KB | +79.0KB | +8.5KB | | +512bytes | +3.5KB |
+| net10.0 | 10.0KB | 66.0KB | +56.0KB | +9.0KB | | +512bytes | +3.5KB |
+| net11.0 | 10.0KB | 27.0KB | +17.0KB | +9.0KB | | +1.0KB | +3.5KB |
### Assembly Sizes with EmbedUntrackedSources
| | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability |
|----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------|
-| netstandard2.0 | 8.0KB | 498.3KB | +490.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netstandard2.1 | 8.5KB | 427.1KB | +418.6KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net461 | 8.5KB | 497.8KB | +489.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net462 | 7.0KB | 501.8KB | +494.8KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB |
-| net47 | 7.0KB | 501.1KB | +494.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net471 | 8.5KB | 499.7KB | +491.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net472 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB |
-| net48 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB |
-| net481 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.0 | 9.0KB | 463.7KB | +454.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.1 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.2 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp3.0 | 9.5KB | 417.0KB | +407.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp3.1 | 9.5KB | 415.0KB | +405.5KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
-| net5.0 | 9.5KB | 360.8KB | +351.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
-| net6.0 | 10.0KB | 281.8KB | +271.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB |
-| net7.0 | 10.0KB | 225.8KB | +215.8KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB |
-| net8.0 | 9.5KB | 182.2KB | +172.7KB | +16.0KB | +299bytes | +1.1KB | +4.2KB |
-| net9.0 | 9.5KB | 114.7KB | +105.2KB | +16.0KB | | +1.1KB | +4.2KB |
-| net10.0 | 10.0KB | 83.9KB | +73.9KB | +16.5KB | | +1.1KB | +4.2KB |
-| net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB |
+| netstandard2.0 | 8.0KB | 507.8KB | +499.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| netstandard2.1 | 8.5KB | 436.6KB | +428.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| net461 | 8.5KB | 507.8KB | +499.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net462 | 7.0KB | 511.3KB | +504.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net47 | 7.0KB | 510.6KB | +503.6KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
+| net471 | 8.5KB | 509.7KB | +501.2KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB |
+| net472 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net48 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net481 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.0 | 9.0KB | 473.2KB | +464.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.1 | 9.0KB | 442.8KB | +433.8KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.2 | 9.0KB | 442.8KB | +433.8KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp3.0 | 9.5KB | 426.5KB | +417.0KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| netcoreapp3.1 | 9.5KB | 425.0KB | +415.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net5.0 | 9.5KB | 370.3KB | +360.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
+| net6.0 | 10.0KB | 292.3KB | +282.3KB | +17.7KB | +8.2KB | +1.1KB | +4.2KB |
+| net7.0 | 10.0KB | 236.2KB | +226.2KB | +16.6KB | +6.9KB | +1.1KB | +3.7KB |
+| net8.0 | 9.5KB | 192.7KB | +183.2KB | +15.5KB | +299bytes | +1.1KB | +3.7KB |
+| net9.0 | 9.5KB | 125.2KB | +115.7KB | +16.0KB | | +1.1KB | +4.2KB |
+| net10.0 | 10.0KB | 94.4KB | +84.4KB | +16.5KB | | +1.1KB | +4.2KB |
+| net11.0 | 10.0KB | 41.3KB | +31.3KB | +16.5KB | | +1.6KB | +4.2KB |
diff --git a/readme.md b/readme.md
index 63ae2db0..f3a2578c 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: 1016**
+**API count: 1020**
### Per Target Framework
| Target | APIs |
| -- | -- |
-| `net461` | 963 |
-| `net462` | 963 |
-| `net47` | 962 |
-| `net471` | 961 |
-| `net472` | 957 |
-| `net48` | 957 |
-| `net481` | 957 |
-| `netstandard2.0` | 959 |
-| `netstandard2.1` | 811 |
-| `netcoreapp2.0` | 880 |
-| `netcoreapp2.1` | 822 |
-| `netcoreapp2.2` | 822 |
-| `netcoreapp3.0` | 769 |
-| `netcoreapp3.1` | 768 |
-| `net5.0` | 640 |
-| `net6.0` | 545 |
-| `net7.0` | 392 |
-| `net8.0` | 273 |
-| `net9.0` | 179 |
-| `net10.0` | 126 |
-| `net11.0` | 58 |
-| `uap10.0` | 949 |
+| `net461` | 967 |
+| `net462` | 967 |
+| `net47` | 966 |
+| `net471` | 965 |
+| `net472` | 961 |
+| `net48` | 961 |
+| `net481` | 961 |
+| `netstandard2.0` | 963 |
+| `netstandard2.1` | 815 |
+| `netcoreapp2.0` | 884 |
+| `netcoreapp2.1` | 826 |
+| `netcoreapp2.2` | 826 |
+| `netcoreapp3.0` | 773 |
+| `netcoreapp3.1` | 772 |
+| `net5.0` | 644 |
+| `net6.0` | 549 |
+| `net7.0` | 396 |
+| `net8.0` | 277 |
+| `net9.0` | 183 |
+| `net10.0` | 130 |
+| `net11.0` | 62 |
+| `uap10.0` | 953 |
@@ -96,54 +96,54 @@ 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 | 342.5KB | +334.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netstandard2.1 | 8.5KB | 297.0KB | +288.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net461 | 8.5KB | 341.0KB | +332.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net462 | 7.0KB | 345.0KB | +338.0KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB |
-| net47 | 7.0KB | 344.5KB | +337.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net471 | 8.5KB | 343.5KB | +335.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| net472 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB |
-| net48 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.0KB | +9.0KB | +13.5KB |
-| net481 | 8.5KB | 342.5KB | +334.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.0 | 9.0KB | 318.5KB | +309.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.1 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp2.2 | 9.0KB | 299.5KB | +290.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp3.0 | 9.5KB | 292.0KB | +282.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
-| netcoreapp3.1 | 9.5KB | 290.0KB | +280.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
-| net5.0 | 9.5KB | 254.0KB | +244.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
-| net6.0 | 10.0KB | 195.0KB | +185.0KB | +10.0KB | +7.0KB | +1.0KB | +3.5KB |
-| net7.0 | 10.0KB | 157.5KB | +147.5KB | +9.5KB | +5.5KB | +512bytes | +3.5KB |
-| net8.0 | 9.5KB | 129.0KB | +119.5KB | +8.5KB | | +512bytes | +3.5KB |
-| net9.0 | 9.5KB | 82.5KB | +73.0KB | +8.5KB | | +512bytes | +3.5KB |
-| net10.0 | 10.0KB | 60.0KB | +50.0KB | +9.0KB | | +512bytes | +3.5KB |
-| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB |
+| netstandard2.0 | 8.0KB | 347.5KB | +339.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| netstandard2.1 | 8.5KB | 302.0KB | +293.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| net461 | 8.5KB | 346.5KB | +338.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net462 | 7.0KB | 350.0KB | +343.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net47 | 7.0KB | 349.5KB | +342.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
+| net471 | 8.5KB | 349.0KB | +340.5KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB |
+| net472 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net48 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net481 | 8.5KB | 347.5KB | +339.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.0 | 9.0KB | 323.5KB | +314.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.1 | 9.0KB | 304.5KB | +295.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp2.2 | 9.0KB | 304.5KB | +295.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| netcoreapp3.0 | 9.5KB | 297.0KB | +287.5KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB |
+| netcoreapp3.1 | 9.5KB | 295.5KB | +286.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB |
+| net5.0 | 9.5KB | 259.0KB | +249.5KB | +9.0KB | +6.5KB | +9.5KB | +14.0KB |
+| net6.0 | 10.0KB | 201.0KB | +191.0KB | +10.0KB | +6.5KB | +512bytes | +3.5KB |
+| net7.0 | 10.0KB | 163.5KB | +153.5KB | +9.0KB | +5.5KB | +512bytes | +3.0KB |
+| net8.0 | 9.5KB | 135.0KB | +125.5KB | +8.0KB | | +512bytes | +3.0KB |
+| net9.0 | 9.5KB | 88.5KB | +79.0KB | +8.5KB | | +512bytes | +3.5KB |
+| net10.0 | 10.0KB | 66.0KB | +56.0KB | +9.0KB | | +512bytes | +3.5KB |
+| net11.0 | 10.0KB | 27.0KB | +17.0KB | +9.0KB | | +1.0KB | +3.5KB |
### Assembly Sizes with EmbedUntrackedSources
| | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability |
|----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------|
-| netstandard2.0 | 8.0KB | 498.3KB | +490.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netstandard2.1 | 8.5KB | 427.1KB | +418.6KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net461 | 8.5KB | 497.8KB | +489.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net462 | 7.0KB | 501.8KB | +494.8KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB |
-| net47 | 7.0KB | 501.1KB | +494.1KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net471 | 8.5KB | 499.7KB | +491.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| net472 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB |
-| net48 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +7.7KB | +13.9KB | +18.9KB |
-| net481 | 8.5KB | 497.7KB | +489.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.0 | 9.0KB | 463.7KB | +454.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.1 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp2.2 | 9.0KB | 433.3KB | +424.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp3.0 | 9.5KB | 417.0KB | +407.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
-| netcoreapp3.1 | 9.5KB | 415.0KB | +405.5KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
-| net5.0 | 9.5KB | 360.8KB | +351.3KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
-| net6.0 | 10.0KB | 281.8KB | +271.8KB | +17.7KB | +8.7KB | +1.6KB | +4.2KB |
-| net7.0 | 10.0KB | 225.8KB | +215.8KB | +17.1KB | +6.9KB | +1.1KB | +4.2KB |
-| net8.0 | 9.5KB | 182.2KB | +172.7KB | +16.0KB | +299bytes | +1.1KB | +4.2KB |
-| net9.0 | 9.5KB | 114.7KB | +105.2KB | +16.0KB | | +1.1KB | +4.2KB |
-| net10.0 | 10.0KB | 83.9KB | +73.9KB | +16.5KB | | +1.1KB | +4.2KB |
-| net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB |
+| netstandard2.0 | 8.0KB | 507.8KB | +499.8KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| netstandard2.1 | 8.5KB | 436.6KB | +428.1KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| net461 | 8.5KB | 507.8KB | +499.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net462 | 7.0KB | 511.3KB | +504.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net47 | 7.0KB | 510.6KB | +503.6KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
+| net471 | 8.5KB | 509.7KB | +501.2KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB |
+| net472 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net48 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net481 | 8.5KB | 507.2KB | +498.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.0 | 9.0KB | 473.2KB | +464.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.1 | 9.0KB | 442.8KB | +433.8KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp2.2 | 9.0KB | 442.8KB | +433.8KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| netcoreapp3.0 | 9.5KB | 426.5KB | +417.0KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB |
+| netcoreapp3.1 | 9.5KB | 425.0KB | +415.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB |
+| net5.0 | 9.5KB | 370.3KB | +360.8KB | +16.7KB | +8.2KB | +14.4KB | +19.4KB |
+| net6.0 | 10.0KB | 292.3KB | +282.3KB | +17.7KB | +8.2KB | +1.1KB | +4.2KB |
+| net7.0 | 10.0KB | 236.2KB | +226.2KB | +16.6KB | +6.9KB | +1.1KB | +3.7KB |
+| net8.0 | 9.5KB | 192.7KB | +183.2KB | +15.5KB | +299bytes | +1.1KB | +3.7KB |
+| net9.0 | 9.5KB | 125.2KB | +115.7KB | +16.0KB | | +1.1KB | +4.2KB |
+| net10.0 | 10.0KB | 94.4KB | +84.4KB | +16.5KB | | +1.1KB | +4.2KB |
+| net11.0 | 10.0KB | 41.3KB | +31.3KB | +16.5KB | | +1.6KB | +4.2KB |
@@ -2035,6 +2035,14 @@ The class `Polyfill` includes the following extension methods:
#### TaskCompletionSource
#### UnreachableException
+
+#### StringStream
+
+#### ReadOnlyMemoryStream
+
+#### WritableMemoryStream
+
+#### ReadOnlySequenceStream
@@ -2233,7 +2241,7 @@ void ObjectDisposedExceptionExample(bool isDisposed)
ObjectDisposedException.ThrowIf(isDisposed, typeof(Consume));
}
```
-snippet source | anchor
+snippet source | anchor
@@ -2252,7 +2260,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 047b78a3..77d97df2 100644
--- a/src/ApiBuilderTests/BuildApiTest.cs
+++ b/src/ApiBuilderTests/BuildApiTest.cs
@@ -23,6 +23,10 @@ public Task RunWithRoslyn()
WriteHelper("Utf16", writer, ref count);
WriteType(nameof(TaskCompletionSource), writer, ref count);
WriteType(nameof(UnreachableException), writer, ref count);
+ WriteType("StringStream", writer, ref count);
+ WriteType("ReadOnlyMemoryStream", writer, ref count);
+ WriteType("WritableMemoryStream", writer, ref count);
+ WriteType("ReadOnlySequenceStream", writer, ref count);
count += Directory.EnumerateFiles(polyfillDir, "*Attribute.cs", SearchOption.AllDirectories).Count();
// Index and Range
diff --git a/src/ApiBuilderTests/StreamWrapperBclDetectionTests.cs b/src/ApiBuilderTests/StreamWrapperBclDetectionTests.cs
new file mode 100644
index 00000000..fe4bda33
--- /dev/null
+++ b/src/ApiBuilderTests/StreamWrapperBclDetectionTests.cs
@@ -0,0 +1,84 @@
+using System.Text;
+
+///
+/// The Stream wrapper types from dotnet/runtime#126669 (StringStream, ReadOnlyMemoryStream,
+/// WritableMemoryStream, ReadOnlySequenceStream) were approved/merged for net11 but are NOT in the
+/// current net11 preview/RC. Polyfill therefore ships them as ACTIVE on net11 too.
+///
+/// This project (ApiBuilderTests) targets net11.0 and does NOT compile the polyfill source into
+/// itself, so it keeps building once the BCL gains these types. This test runs on the net11 runtime
+/// and fails the moment the real types appear, telling you exactly which guards to flip.
+///
+public class StreamWrapperBclDetectionTests
+{
+ static readonly (string FullName, string File)[] pending =
+ [
+ ("System.IO.StringStream", "StringStream.cs"),
+ ("System.IO.ReadOnlyMemoryStream", "ReadOnlyMemoryStream.cs"),
+ ("System.IO.WritableMemoryStream", "WritableMemoryStream.cs"),
+ ("System.Buffers.ReadOnlySequenceStream", "ReadOnlySequenceStream.cs"),
+ ];
+
+ [Test]
+ public void StreamWrapperPolyfillsStillNeededOnNet11()
+ {
+ var present = new List<(string FullName, string File, string Assembly)>();
+ foreach (var (fullName, file) in pending)
+ {
+ var type = FindBclType(fullName);
+ if (type != null)
+ {
+ present.Add((fullName, file, type.Assembly.GetName().Name!));
+ }
+ }
+
+ if (present.Count == 0)
+ {
+ return;
+ }
+
+ var builder = new StringBuilder();
+ builder.AppendLine($"{present.Count} Stream wrapper type(s) from dotnet/runtime#126669 are now provided by the BCL");
+ builder.AppendLine($"on this runtime ({System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}).");
+ builder.AppendLine("The matching Polyfill source now collides with the real type and must be retired for net11.");
+ builder.AppendLine("For EACH type below, edit the listed file under src/Polyfill/:");
+ builder.AppendLine();
+
+ foreach (var (fullName, file, assembly) in present)
+ {
+ builder.AppendLine($" {fullName} (now in {assembly})");
+ builder.AppendLine($" file: src/Polyfill/{file}");
+ builder.AppendLine(" 1. change `#if FeatureMemory` to `#if FeatureMemory && !NET11_0_OR_GREATER`");
+ builder.AppendLine(" 2. append after the final #endif:");
+ builder.AppendLine(" #if NET11_0_OR_GREATER");
+ builder.AppendLine($" [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof({fullName}))]");
+ builder.AppendLine(" #endif");
+ builder.AppendLine();
+ }
+
+ builder.AppendLine("Then re-run ApiBuilderTests in Debug to regenerate Split + api_list, and drop any now-redundant");
+ builder.AppendLine("usage from src/Consume/Consume.cs.");
+
+ throw new(builder.ToString());
+ }
+
+ static System.Type? FindBclType(string fullName)
+ {
+ foreach (var assembly in new[]
+ {
+ "System.Private.CoreLib",
+ "System.Runtime",
+ "System.Memory",
+ "mscorlib"
+ })
+ {
+ var type = System.Type.GetType($"{fullName}, {assembly}", throwOnError: false);
+ if (type is { IsPublic: true })
+ {
+ return type;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs
index 443df782..cc72a8e8 100644
--- a/src/Consume/Consume.cs
+++ b/src/Consume/Consume.cs
@@ -258,6 +258,39 @@ void Base64UrlUsage()
}
#endif
+#if FeatureMemory
+ void StreamWrappersUsage()
+ {
+ var bytes = new byte[10];
+
+ using (Stream stringStream = new StringStream("text", Encoding.UTF8))
+ {
+ stringStream.Read(bytes, 0, bytes.Length);
+ }
+
+ using (var stringStream = new StringStream("text".AsMemory(), Encoding.UTF8))
+ {
+ Encoding encoding = stringStream.Encoding;
+ stringStream.ReadByte();
+ }
+
+ using (MemoryStream readOnly = new ReadOnlyMemoryStream(new ReadOnlyMemory(bytes)))
+ {
+ readOnly.Read(bytes, 0, bytes.Length);
+ }
+
+ using (MemoryStream writable = new WritableMemoryStream(new Memory(bytes)))
+ {
+ writable.Write(bytes, 0, bytes.Length);
+ }
+
+ using (Stream sequenceStream = new System.Buffers.ReadOnlySequenceStream(new System.Buffers.ReadOnlySequence(bytes)))
+ {
+ sequenceStream.Read(bytes, 0, bytes.Length);
+ }
+ }
+#endif
+
#if FeatureMemory && !RefsBclMemory
void Base64Usage()
{
diff --git a/src/Polyfill/ReadOnlyMemoryStream.cs b/src/Polyfill/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..13bb791b
--- /dev/null
+++ b/src/Polyfill/ReadOnlyMemoryStream.cs
@@ -0,0 +1,74 @@
+#nullable enable
+
+// === Polyfill target window =====================================================================
+// System.IO.ReadOnlyMemoryStream was approved and merged into dotnet/runtime for net11
+// (https://github.com/dotnet/runtime/pull/126669) but is NOT in the current net11 preview/RC SDK,
+// so this polyfill is intentionally ACTIVE on net11 as well.
+// When you upgrade to a net11 SDK that actually ships this type, the build will collide (CS0436)
+// and the ApiBuilderTests "StreamWrapperBclDetectionTests" test FAILS with these exact steps.
+// To retire this polyfill for net11:
+// 1. change `#if FeatureMemory` below to `#if FeatureMemory && !NET11_0_OR_GREATER`
+// 2. add at the bottom of this file (after the final #endif):
+// #if NET11_0_OR_GREATER
+// [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.ReadOnlyMemoryStream))]
+// #endif
+// 3. re-run ApiBuilderTests in Debug to regenerate Split + api_list.
+// ================================================================================================
+#if FeatureMemory
+
+#pragma warning disable
+
+namespace System.IO;
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.readonlymemorystream?view=net-11.0
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.readonlymemorystream.-ctor?view=net-11.0
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+
+#endif
diff --git a/src/Polyfill/ReadOnlySequenceStream.cs b/src/Polyfill/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..be82092c
--- /dev/null
+++ b/src/Polyfill/ReadOnlySequenceStream.cs
@@ -0,0 +1,236 @@
+#nullable enable
+
+// === Polyfill target window =====================================================================
+// System.Buffers.ReadOnlySequenceStream was approved and merged into dotnet/runtime for net11
+// (https://github.com/dotnet/runtime/pull/126669) but is NOT in the current net11 preview/RC SDK,
+// so this polyfill is intentionally ACTIVE on net11 as well.
+// When you upgrade to a net11 SDK that actually ships this type, the build will collide (CS0436)
+// and the ApiBuilderTests "StreamWrapperBclDetectionTests" test FAILS with these exact steps.
+// To retire this polyfill for net11:
+// 1. change `#if FeatureMemory` below to `#if FeatureMemory && !NET11_0_OR_GREATER`
+// 2. add at the bottom of this file (after the final #endif):
+// #if NET11_0_OR_GREATER
+// [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Buffers.ReadOnlySequenceStream))]
+// #endif
+// 3. re-run ApiBuilderTests in Debug to regenerate Split + api_list.
+// ================================================================================================
+#if FeatureMemory
+
+#pragma warning disable
+
+namespace System.Buffers;
+
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+//Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequencestream?view=net-11.0
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequencestream.-ctor?view=net-11.0
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+
+ ///
+ public override bool CanRead => !disposed;
+
+ ///
+ public override bool CanSeek => !disposed;
+
+ ///
+ public override bool CanWrite => false;
+
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ position = value;
+ }
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+
+ position = newPosition;
+ return position;
+ }
+
+ ///
+ public override void Flush()
+ {
+ }
+
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+
+#endif
diff --git a/src/Polyfill/StringStream.cs b/src/Polyfill/StringStream.cs
new file mode 100644
index 00000000..87cd6090
--- /dev/null
+++ b/src/Polyfill/StringStream.cs
@@ -0,0 +1,241 @@
+#nullable enable
+
+// === Polyfill target window =====================================================================
+// System.IO.StringStream was approved and merged into dotnet/runtime for net11
+// (https://github.com/dotnet/runtime/pull/126669) but is NOT in the current net11 preview/RC SDK,
+// so this polyfill is intentionally ACTIVE on net11 as well.
+// When you upgrade to a net11 SDK that actually ships this type, the build will collide (CS0436)
+// and the ApiBuilderTests "StreamWrapperBclDetectionTests" test FAILS with these exact steps.
+// To retire this polyfill for net11:
+// 1. change `#if FeatureMemory` below to `#if FeatureMemory && !NET11_0_OR_GREATER`
+// 2. add at the bottom of this file (after the final #endif):
+// #if NET11_0_OR_GREATER
+// [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.StringStream))]
+// #endif
+// 3. re-run ApiBuilderTests in Debug to regenerate Split + api_list.
+// ================================================================================================
+#if FeatureMemory
+
+#pragma warning disable
+
+namespace System.IO;
+
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.stringstream?view=net-11.0
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.stringstream.-ctor?view=net-11.0#system-io-stringstream-ctor(system-string-system-text-encoding)
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.stringstream.-ctor?view=net-11.0#system-io-stringstream-ctor(system-readonlymemory((system-char))-system-text-encoding)
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+
+ this.text = text;
+ this.encoding = encoding;
+ }
+
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.stringstream.encoding?view=net-11.0
+ public Encoding Encoding => encoding;
+
+ ///
+ public override bool CanRead => !disposed;
+
+ ///
+ public override bool CanSeek => false;
+
+ ///
+ public override bool CanWrite => false;
+
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+
+ return encoded;
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+
+ return -1;
+ }
+
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+
+ ///
+ public override void Flush()
+ {
+ }
+
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+
+#endif
diff --git a/src/Polyfill/WritableMemoryStream.cs b/src/Polyfill/WritableMemoryStream.cs
new file mode 100644
index 00000000..5189f658
--- /dev/null
+++ b/src/Polyfill/WritableMemoryStream.cs
@@ -0,0 +1,75 @@
+#nullable enable
+
+// === Polyfill target window =====================================================================
+// System.IO.WritableMemoryStream was approved and merged into dotnet/runtime for net11
+// (https://github.com/dotnet/runtime/pull/126669) but is NOT in the current net11 preview/RC SDK,
+// so this polyfill is intentionally ACTIVE on net11 as well.
+// When you upgrade to a net11 SDK that actually ships this type, the build will collide (CS0436)
+// and the ApiBuilderTests "StreamWrapperBclDetectionTests" test FAILS with these exact steps.
+// To retire this polyfill for net11:
+// 1. change `#if FeatureMemory` below to `#if FeatureMemory && !NET11_0_OR_GREATER`
+// 2. add at the bottom of this file (after the final #endif):
+// #if NET11_0_OR_GREATER
+// [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IO.WritableMemoryStream))]
+// #endif
+// 3. re-run ApiBuilderTests in Debug to regenerate Split + api_list.
+// ================================================================================================
+#if FeatureMemory
+
+#pragma warning disable
+
+namespace System.IO;
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+//Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.writablememorystream?view=net-11.0
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ //Link: https://learn.microsoft.com/en-us/dotnet/api/system.io.writablememorystream.-ctor?view=net-11.0
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ // The base constructor reports the whole region as content; reset to an empty, ready-to-write stream.
+ base.SetLength(0);
+
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+
+#endif
diff --git a/src/Split/net10.0/ReadOnlyMemoryStream.cs b/src/Split/net10.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net10.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net10.0/ReadOnlySequenceStream.cs b/src/Split/net10.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net10.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net10.0/StringStream.cs b/src/Split/net10.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net10.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net10.0/WritableMemoryStream.cs b/src/Split/net10.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net10.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net11.0/ReadOnlyMemoryStream.cs b/src/Split/net11.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net11.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net11.0/ReadOnlySequenceStream.cs b/src/Split/net11.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net11.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net11.0/StringStream.cs b/src/Split/net11.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net11.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net11.0/WritableMemoryStream.cs b/src/Split/net11.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net11.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net461/ReadOnlyMemoryStream.cs b/src/Split/net461/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net461/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net461/ReadOnlySequenceStream.cs b/src/Split/net461/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net461/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net461/StringStream.cs b/src/Split/net461/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net461/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net461/WritableMemoryStream.cs b/src/Split/net461/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net461/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net462/ReadOnlyMemoryStream.cs b/src/Split/net462/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net462/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net462/ReadOnlySequenceStream.cs b/src/Split/net462/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net462/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net462/StringStream.cs b/src/Split/net462/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net462/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net462/WritableMemoryStream.cs b/src/Split/net462/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net462/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net47/ReadOnlyMemoryStream.cs b/src/Split/net47/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net47/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net47/ReadOnlySequenceStream.cs b/src/Split/net47/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net47/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net47/StringStream.cs b/src/Split/net47/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net47/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net47/WritableMemoryStream.cs b/src/Split/net47/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net47/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net471/ReadOnlyMemoryStream.cs b/src/Split/net471/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net471/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net471/ReadOnlySequenceStream.cs b/src/Split/net471/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net471/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net471/StringStream.cs b/src/Split/net471/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net471/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net471/WritableMemoryStream.cs b/src/Split/net471/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net471/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net472/ReadOnlyMemoryStream.cs b/src/Split/net472/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net472/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net472/ReadOnlySequenceStream.cs b/src/Split/net472/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net472/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net472/StringStream.cs b/src/Split/net472/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net472/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net472/WritableMemoryStream.cs b/src/Split/net472/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net472/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net48/ReadOnlyMemoryStream.cs b/src/Split/net48/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net48/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net48/ReadOnlySequenceStream.cs b/src/Split/net48/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net48/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net48/StringStream.cs b/src/Split/net48/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net48/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net48/WritableMemoryStream.cs b/src/Split/net48/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net48/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net481/ReadOnlyMemoryStream.cs b/src/Split/net481/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net481/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net481/ReadOnlySequenceStream.cs b/src/Split/net481/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net481/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net481/StringStream.cs b/src/Split/net481/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net481/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net481/WritableMemoryStream.cs b/src/Split/net481/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net481/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net5.0/ReadOnlyMemoryStream.cs b/src/Split/net5.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net5.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net5.0/ReadOnlySequenceStream.cs b/src/Split/net5.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net5.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net5.0/StringStream.cs b/src/Split/net5.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net5.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net5.0/WritableMemoryStream.cs b/src/Split/net5.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net5.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net6.0/ReadOnlyMemoryStream.cs b/src/Split/net6.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net6.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net6.0/ReadOnlySequenceStream.cs b/src/Split/net6.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net6.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net6.0/StringStream.cs b/src/Split/net6.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net6.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net6.0/WritableMemoryStream.cs b/src/Split/net6.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net6.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net7.0/ReadOnlyMemoryStream.cs b/src/Split/net7.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net7.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net7.0/ReadOnlySequenceStream.cs b/src/Split/net7.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net7.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net7.0/StringStream.cs b/src/Split/net7.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net7.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net7.0/WritableMemoryStream.cs b/src/Split/net7.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net7.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net8.0/ReadOnlyMemoryStream.cs b/src/Split/net8.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net8.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net8.0/ReadOnlySequenceStream.cs b/src/Split/net8.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net8.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net8.0/StringStream.cs b/src/Split/net8.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net8.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net8.0/WritableMemoryStream.cs b/src/Split/net8.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net8.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/net9.0/ReadOnlyMemoryStream.cs b/src/Split/net9.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/net9.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/net9.0/ReadOnlySequenceStream.cs b/src/Split/net9.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/net9.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net9.0/StringStream.cs b/src/Split/net9.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/net9.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/net9.0/WritableMemoryStream.cs b/src/Split/net9.0/WritableMemoryStream.cs
new file mode 100644
index 00000000..f8e0ecb6
--- /dev/null
+++ b/src/Split/net9.0/WritableMemoryStream.cs
@@ -0,0 +1,51 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-write over a with fixed capacity.
+///
+///
+/// The stream cannot expand beyond the initial memory capacity.
+/// throws and returns .
+/// The polyfill requires an array-backed ; memory backed by native (or other non-array)
+/// buffers is not supported and the constructor throws .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class WritableMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public WritableMemoryStream(Memory buffer)
+ : base(GetArray(buffer, out var offset, out var count), offset, count, writable: true, publiclyVisible: false) =>
+ base.SetLength(0);
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Memory stream is not expandable.");
+ static byte[] GetArray(Memory buffer, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray((ReadOnlyMemory)buffer, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ throw new NotSupportedException("WritableMemoryStream polyfill requires an array-backed Memory.");
+ }
+}
+#endif
diff --git a/src/Split/netcoreapp2.0/ReadOnlyMemoryStream.cs b/src/Split/netcoreapp2.0/ReadOnlyMemoryStream.cs
new file mode 100644
index 00000000..7a03bfdb
--- /dev/null
+++ b/src/Split/netcoreapp2.0/ReadOnlyMemoryStream.cs
@@ -0,0 +1,52 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The stream cannot be written to. always returns .
+/// throws and returns .
+/// When the supplied memory is not backed by an array (for example native memory) the polyfill copies it;
+/// the BCL always wraps the memory without copying.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlyMemoryStream :
+ MemoryStream
+{
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlyMemoryStream(ReadOnlyMemory source)
+ : base(GetArray(source, out var offset, out var count), offset, count, writable: false, publiclyVisible: false)
+ {
+ }
+ static byte[] GetArray(ReadOnlyMemory source, out int offset, out int count)
+ {
+ if (MemoryMarshal.TryGetArray(source, out var segment) &&
+ segment.Array != null)
+ {
+ offset = segment.Offset;
+ count = segment.Count;
+ return segment.Array;
+ }
+ var array = source.ToArray();
+ offset = 0;
+ count = array.Length;
+ return array;
+ }
+}
+#endif
diff --git a/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs b/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs
new file mode 100644
index 00000000..1b9bd979
--- /dev/null
+++ b/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs
@@ -0,0 +1,185 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.Buffers;
+using System.IO;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a seekable, read-only over a .
+///
+///
+/// The underlying sequence is not copied; reads are served directly from its segments.
+/// The stream cannot be written to. always returns .
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class ReadOnlySequenceStream :
+ Stream
+{
+ ReadOnlySequence sequence;
+ long position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class over the specified .
+ ///
+ public ReadOnlySequenceStream(ReadOnlySequence source) =>
+ sequence = source;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => !disposed;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return sequence.Length;
+ }
+ }
+ ///
+ public override long Position
+ {
+ get
+ {
+ ThrowIfDisposed();
+ return position;
+ }
+ set
+ {
+ ThrowIfDisposed();
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ position = value;
+ }
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return 0;
+ }
+ var remaining = sequence.Slice(position);
+ var toRead = (int)Math.Min(remaining.Length, count);
+ if (toRead <= 0)
+ {
+ return 0;
+ }
+ remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead));
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ if (position >= sequence.Length)
+ {
+ return -1;
+ }
+ var result = sequence.Slice(position, 1).First.Span[0];
+ position++;
+ return result;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException(exception);
+ }
+ }
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ var basePosition = origin switch
+ {
+ SeekOrigin.Begin => 0L,
+ SeekOrigin.Current => position,
+ SeekOrigin.End => sequence.Length,
+ _ => throw new ArgumentException("Invalid seek origin.", nameof(origin))
+ };
+ var newPosition = basePosition + offset;
+ if (newPosition < 0)
+ {
+ throw new IOException("An attempt was made to move the position before the beginning of the stream.");
+ }
+ position = newPosition;
+ return position;
+ }
+ ///
+ public override void Flush()
+ {
+ }
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken) =>
+ cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask;
+ ///
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ public override void Write(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Stream does not support writing.");
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ disposed = true;
+ sequence = default;
+ base.Dispose(disposing);
+ }
+ void ThrowIfDisposed()
+ {
+ if (disposed)
+ {
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+ }
+ static void GuardRange(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ if (buffer.Length - offset < count)
+ {
+ throw new ArgumentException("The sum of offset and count is larger than the buffer length.");
+ }
+ }
+}
+#endif
diff --git a/src/Split/netcoreapp2.0/StringStream.cs b/src/Split/netcoreapp2.0/StringStream.cs
new file mode 100644
index 00000000..b4cd3943
--- /dev/null
+++ b/src/Split/netcoreapp2.0/StringStream.cs
@@ -0,0 +1,186 @@
+//
+#pragma warning disable
+#nullable enable
+#if FeatureMemory
+#pragma warning disable
+namespace System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+///
+/// Provides a read-only, non-seekable that encodes a or
+/// into bytes using a specified .
+///
+///
+/// This stream never emits a byte order mark (BOM). Callers who need a BOM can prepend it themselves.
+/// The polyfill encodes the whole text into a single buffer on first read rather than encoding
+/// on-the-fly; this is a non-observable performance difference from the BCL implementation.
+///
+[ExcludeFromCodeCoverage]
+[DebuggerNonUserCode]
+#if PolyUseEmbeddedAttribute
+[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
+#endif
+#if PolyPublic
+public
+#endif
+sealed class StringStream :
+ Stream
+{
+ ReadOnlyMemory text;
+ Encoding encoding;
+ byte[]? encoded;
+ int position;
+ bool disposed;
+ ///
+ /// Initializes a new instance of the class with the specified string and encoding.
+ ///
+ public StringStream(string text, Encoding encoding)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text.AsMemory();
+ this.encoding = encoding;
+ }
+ ///
+ /// Initializes a new instance of the class with the specified character memory and encoding.
+ ///
+ public StringStream(ReadOnlyMemory text, Encoding encoding)
+ {
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
+ this.text = text;
+ this.encoding = encoding;
+ }
+ ///
+ /// Gets the encoding used by this stream.
+ ///
+ public Encoding Encoding => encoding;
+ ///
+ public override bool CanRead => !disposed;
+ ///
+ public override bool CanSeek => false;
+ ///
+ public override bool CanWrite => false;
+ ///
+ public override long Length => throw new NotSupportedException("Stream does not support seeking.");
+ ///
+ public override long Position
+ {
+ get => throw new NotSupportedException("Stream does not support seeking.");
+ set => throw new NotSupportedException("Stream does not support seeking.");
+ }
+ byte[] GetEncoded()
+ {
+ if (encoded == null)
+ {
+ var chars = text.ToArray();
+ encoded = encoding.GetBytes(chars, 0, chars.Length);
+ }
+ return encoded;
+ }
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ GuardRange(buffer, offset, count);
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ var remaining = all.Length - position;
+ if (remaining <= 0 ||
+ count == 0)
+ {
+ return 0;
+ }
+ var toRead = Math.Min(remaining, count);
+ Array.Copy(all, position, buffer, offset, toRead);
+ position += toRead;
+ return toRead;
+ }
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ var all = GetEncoded();
+ if (position < all.Length)
+ {
+ return all[position++];
+ }
+ return -1;
+ }
+ ///
+ public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ GuardRange(buffer, offset, count);
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ try
+ {
+ return Task.FromResult(Read(buffer, offset, count));
+ }
+ catch (Exception exception)
+ {
+ return Task.FromException