diff --git a/apiCount.include.md b/apiCount.include.md index 41ee45b3..876081b7 100644 --- a/apiCount.include.md +++ b/apiCount.include.md @@ -4,25 +4,25 @@ | Target | APIs | | -- | -- | -| `net461` | 972 | -| `net462` | 972 | -| `net47` | 971 | -| `net471` | 970 | -| `net472` | 966 | -| `net48` | 966 | -| `net481` | 966 | -| `netstandard2.0` | 968 | -| `netstandard2.1` | 821 | -| `netcoreapp2.0` | 891 | -| `netcoreapp2.1` | 832 | -| `netcoreapp2.2` | 832 | -| `netcoreapp3.0` | 779 | -| `netcoreapp3.1` | 778 | -| `net5.0` | 650 | -| `net6.0` | 555 | +| `net461` | 973 | +| `net462` | 973 | +| `net47` | 972 | +| `net471` | 971 | +| `net472` | 967 | +| `net48` | 967 | +| `net481` | 967 | +| `netstandard2.0` | 969 | +| `netstandard2.1` | 822 | +| `netcoreapp2.0` | 892 | +| `netcoreapp2.1` | 833 | +| `netcoreapp2.2` | 833 | +| `netcoreapp3.0` | 785 | +| `netcoreapp3.1` | 784 | +| `net5.0` | 656 | +| `net6.0` | 557 | | `net7.0` | 402 | | `net8.0` | 283 | | `net9.0` | 189 | | `net10.0` | 135 | | `net11.0` | 58 | -| `uap10.0` | 958 | +| `uap10.0` | 959 | diff --git a/assemblySize.include.md b/assemblySize.include.md index dd4f2db9..18f13d19 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 | 358.5KB | +350.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| netstandard2.1 | 8.5KB | 312.5KB | +304.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 357.0KB | +348.5KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | -| net462 | 7.0KB | 360.5KB | +353.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 360.5KB | +353.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 359.5KB | +351.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| net472 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| net48 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| net481 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| netcoreapp2.0 | 9.0KB | 336.0KB | +327.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 316.0KB | +307.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.2 | 9.0KB | 316.0KB | +307.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.0 | 9.5KB | 308.5KB | +299.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.1 | 9.5KB | 307.0KB | +297.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net5.0 | 9.5KB | 271.0KB | +261.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net6.0 | 10.0KB | 212.5KB | +202.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | +| netstandard2.0 | 8.0KB | 358.5KB | +350.5KB | +8.0KB | +7.0KB | +8.0KB | +12.5KB | +| netstandard2.1 | 8.5KB | 313.0KB | +304.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net461 | 8.5KB | 357.5KB | +349.0KB | +7.5KB | +6.5KB | +8.0KB | +12.0KB | +| net462 | 7.0KB | 361.0KB | +354.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net47 | 7.0KB | 361.0KB | +354.0KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB | +| net471 | 8.5KB | 360.0KB | +351.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net472 | 8.5KB | 358.5KB | +350.0KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | +| net48 | 8.5KB | 358.5KB | +350.0KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | +| net481 | 8.5KB | 358.5KB | +350.0KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | +| netcoreapp2.0 | 9.0KB | 336.5KB | +327.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 316.5KB | +307.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 316.5KB | +307.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 309.5KB | +300.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 308.0KB | +298.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net5.0 | 9.5KB | 272.0KB | +262.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net6.0 | 10.0KB | 213.0KB | +203.0KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | | net7.0 | 10.0KB | 175.0KB | +165.0KB | +11.5KB | +8.0KB | +512bytes | +3.0KB | | net8.0 | 9.5KB | 145.5KB | +136.0KB | +8.5KB | | +512bytes | +3.0KB | | net9.0 | 9.5KB | 98.5KB | +89.0KB | +8.5KB | | +512bytes | +3.5KB | | net10.0 | 10.0KB | 76.0KB | +66.0KB | +8.5KB | | +512bytes | +3.5KB | -| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | +| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +1.0KB | +3.5KB | ### Assembly Sizes with EmbedUntrackedSources | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 524.4KB | +516.4KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| netstandard2.1 | 8.5KB | 451.8KB | +443.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 524.0KB | +515.5KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | -| net462 | 7.0KB | 527.5KB | +520.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 527.2KB | +520.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 525.8KB | +517.3KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| net472 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| net48 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| net481 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| netcoreapp2.0 | 9.0KB | 491.3KB | +482.3KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 459.0KB | +450.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.2 | 9.0KB | 459.0KB | +450.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.0 | 9.5KB | 442.7KB | +433.2KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.1 | 9.5KB | 441.2KB | +431.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net5.0 | 9.5KB | 387.0KB | +377.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net6.0 | 10.0KB | 308.4KB | +298.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | +| netstandard2.0 | 8.0KB | 524.8KB | +516.8KB | +15.7KB | +8.7KB | +12.9KB | +17.9KB | +| netstandard2.1 | 8.5KB | 452.7KB | +444.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net461 | 8.5KB | 524.9KB | +516.4KB | +15.2KB | +8.2KB | +12.9KB | +17.4KB | +| net462 | 7.0KB | 528.4KB | +521.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net47 | 7.0KB | 528.1KB | +521.1KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB | +| net471 | 8.5KB | 526.8KB | +518.3KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net472 | 8.5KB | 524.2KB | +515.7KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | +| net48 | 8.5KB | 524.2KB | +515.7KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | +| net481 | 8.5KB | 524.2KB | +515.7KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | +| netcoreapp2.0 | 9.0KB | 492.3KB | +483.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 459.9KB | +450.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 459.9KB | +450.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 444.0KB | +434.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 442.5KB | +433.0KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net5.0 | 9.5KB | 388.3KB | +378.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net6.0 | 10.0KB | 309.1KB | +299.1KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | | net7.0 | 10.0KB | 252.3KB | +242.3KB | +19.1KB | +9.4KB | +1.1KB | +3.7KB | | net8.0 | 9.5KB | 207.3KB | +197.8KB | +16.0KB | +299bytes | +1.1KB | +3.7KB | | net9.0 | 9.5KB | 139.1KB | +129.6KB | +16.0KB | | +1.1KB | +4.2KB | | net10.0 | 10.0KB | 108.0KB | +98.0KB | +16.0KB | | +1.1KB | +4.2KB | -| net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | +| net11.0 | 10.0KB | 30.4KB | +20.4KB | +16.5KB | | +1.6KB | +4.2KB | diff --git a/readme.md b/readme.md index 828151bb..06fbbc90 100644 --- a/readme.md +++ b/readme.md @@ -19,28 +19,28 @@ The package targets `netstandard2.0` and is designed to support the following ru | Target | APIs | | -- | -- | -| `net461` | 972 | -| `net462` | 972 | -| `net47` | 971 | -| `net471` | 970 | -| `net472` | 966 | -| `net48` | 966 | -| `net481` | 966 | -| `netstandard2.0` | 968 | -| `netstandard2.1` | 821 | -| `netcoreapp2.0` | 891 | -| `netcoreapp2.1` | 832 | -| `netcoreapp2.2` | 832 | -| `netcoreapp3.0` | 779 | -| `netcoreapp3.1` | 778 | -| `net5.0` | 650 | -| `net6.0` | 555 | +| `net461` | 973 | +| `net462` | 973 | +| `net47` | 972 | +| `net471` | 971 | +| `net472` | 967 | +| `net48` | 967 | +| `net481` | 967 | +| `netstandard2.0` | 969 | +| `netstandard2.1` | 822 | +| `netcoreapp2.0` | 892 | +| `netcoreapp2.1` | 833 | +| `netcoreapp2.2` | 833 | +| `netcoreapp3.0` | 785 | +| `netcoreapp3.1` | 784 | +| `net5.0` | 656 | +| `net6.0` | 557 | | `net7.0` | 402 | | `net8.0` | 283 | | `net9.0` | 189 | | `net10.0` | 135 | | `net11.0` | 58 | -| `uap10.0` | 958 | +| `uap10.0` | 959 | @@ -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 | 358.5KB | +350.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| netstandard2.1 | 8.5KB | 312.5KB | +304.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net461 | 8.5KB | 357.0KB | +348.5KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | -| net462 | 7.0KB | 360.5KB | +353.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net47 | 7.0KB | 360.5KB | +353.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net471 | 8.5KB | 359.5KB | +351.0KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | -| net472 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| net48 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| net481 | 8.5KB | 358.0KB | +349.5KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | -| netcoreapp2.0 | 9.0KB | 336.0KB | +327.0KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.1 | 9.0KB | 316.0KB | +307.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp2.2 | 9.0KB | 316.0KB | +307.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| netcoreapp3.0 | 9.5KB | 308.5KB | +299.0KB | +9.0KB | +6.5KB | +9.0KB | +14.0KB | -| netcoreapp3.1 | 9.5KB | 307.0KB | +297.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net5.0 | 9.5KB | 271.0KB | +261.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | -| net6.0 | 10.0KB | 212.5KB | +202.5KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | +| netstandard2.0 | 8.0KB | 358.5KB | +350.5KB | +8.0KB | +7.0KB | +8.0KB | +12.5KB | +| netstandard2.1 | 8.5KB | 313.0KB | +304.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net461 | 8.5KB | 357.5KB | +349.0KB | +7.5KB | +6.5KB | +8.0KB | +12.0KB | +| net462 | 7.0KB | 361.0KB | +354.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| net47 | 7.0KB | 361.0KB | +354.0KB | +9.0KB | +6.0KB | +9.0KB | +13.5KB | +| net471 | 8.5KB | 360.0KB | +351.5KB | +7.5KB | +6.5KB | +7.5KB | +12.0KB | +| net472 | 8.5KB | 358.5KB | +350.0KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | +| net48 | 8.5KB | 358.5KB | +350.0KB | +7.5KB | +6.5KB | +8.0KB | +12.5KB | +| net481 | 8.5KB | 358.5KB | +350.0KB | +8.0KB | +6.5KB | +8.0KB | +12.5KB | +| netcoreapp2.0 | 9.0KB | 336.5KB | +327.5KB | +8.5KB | +6.0KB | +8.5KB | +13.5KB | +| netcoreapp2.1 | 9.0KB | 316.5KB | +307.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp2.2 | 9.0KB | 316.5KB | +307.5KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.0 | 9.5KB | 309.5KB | +300.0KB | +9.0KB | +6.5KB | +9.0KB | +13.5KB | +| netcoreapp3.1 | 9.5KB | 308.0KB | +298.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net5.0 | 9.5KB | 272.0KB | +262.5KB | +8.5KB | +6.5KB | +9.0KB | +13.5KB | +| net6.0 | 10.0KB | 213.0KB | +203.0KB | +10.0KB | +7.0KB | +512bytes | +3.5KB | | net7.0 | 10.0KB | 175.0KB | +165.0KB | +11.5KB | +8.0KB | +512bytes | +3.0KB | | net8.0 | 9.5KB | 145.5KB | +136.0KB | +8.5KB | | +512bytes | +3.0KB | | net9.0 | 9.5KB | 98.5KB | +89.0KB | +8.5KB | | +512bytes | +3.5KB | | net10.0 | 10.0KB | 76.0KB | +66.0KB | +8.5KB | | +512bytes | +3.5KB | -| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +512bytes | +3.5KB | +| net11.0 | 10.0KB | 20.5KB | +10.5KB | +9.0KB | | +1.0KB | +3.5KB | ### Assembly Sizes with EmbedUntrackedSources | | Empty Assembly | With Polyfill | Diff | Ensure | ArgumentExceptions | StringInterpolation | Nullability | |----------------|----------------|---------------|-----------|-----------|--------------------|---------------------|-------------| -| netstandard2.0 | 8.0KB | 524.4KB | +516.4KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| netstandard2.1 | 8.5KB | 451.8KB | +443.3KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net461 | 8.5KB | 524.0KB | +515.5KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | -| net462 | 7.0KB | 527.5KB | +520.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net47 | 7.0KB | 527.2KB | +520.2KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net471 | 8.5KB | 525.8KB | +517.3KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | -| net472 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| net48 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| net481 | 8.5KB | 523.3KB | +514.8KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | -| netcoreapp2.0 | 9.0KB | 491.3KB | +482.3KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.1 | 9.0KB | 459.0KB | +450.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp2.2 | 9.0KB | 459.0KB | +450.0KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| netcoreapp3.0 | 9.5KB | 442.7KB | +433.2KB | +16.7KB | +8.2KB | +13.9KB | +19.4KB | -| netcoreapp3.1 | 9.5KB | 441.2KB | +431.7KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net5.0 | 9.5KB | 387.0KB | +377.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | -| net6.0 | 10.0KB | 308.4KB | +298.4KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | +| netstandard2.0 | 8.0KB | 524.8KB | +516.8KB | +15.7KB | +8.7KB | +12.9KB | +17.9KB | +| netstandard2.1 | 8.5KB | 452.7KB | +444.2KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net461 | 8.5KB | 524.9KB | +516.4KB | +15.2KB | +8.2KB | +12.9KB | +17.4KB | +| net462 | 7.0KB | 528.4KB | +521.4KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| net47 | 7.0KB | 528.1KB | +521.1KB | +16.7KB | +7.7KB | +13.9KB | +18.9KB | +| net471 | 8.5KB | 526.8KB | +518.3KB | +15.2KB | +8.2KB | +12.4KB | +17.4KB | +| net472 | 8.5KB | 524.2KB | +515.7KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | +| net48 | 8.5KB | 524.2KB | +515.7KB | +15.2KB | +8.2KB | +12.9KB | +17.9KB | +| net481 | 8.5KB | 524.2KB | +515.7KB | +15.7KB | +8.2KB | +12.9KB | +17.9KB | +| netcoreapp2.0 | 9.0KB | 492.3KB | +483.3KB | +16.2KB | +7.7KB | +13.4KB | +18.9KB | +| netcoreapp2.1 | 9.0KB | 459.9KB | +450.9KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp2.2 | 9.0KB | 459.9KB | +450.9KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.0 | 9.5KB | 444.0KB | +434.5KB | +16.7KB | +8.2KB | +13.9KB | +18.9KB | +| netcoreapp3.1 | 9.5KB | 442.5KB | +433.0KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net5.0 | 9.5KB | 388.3KB | +378.8KB | +16.2KB | +8.2KB | +13.9KB | +18.9KB | +| net6.0 | 10.0KB | 309.1KB | +299.1KB | +17.7KB | +8.7KB | +1.1KB | +4.2KB | | net7.0 | 10.0KB | 252.3KB | +242.3KB | +19.1KB | +9.4KB | +1.1KB | +3.7KB | | net8.0 | 9.5KB | 207.3KB | +197.8KB | +16.0KB | +299bytes | +1.1KB | +3.7KB | | net9.0 | 9.5KB | 139.1KB | +129.6KB | +16.0KB | | +1.1KB | +4.2KB | | net10.0 | 10.0KB | 108.0KB | +98.0KB | +16.0KB | | +1.1KB | +4.2KB | -| net11.0 | 10.0KB | 30.3KB | +20.3KB | +16.5KB | | +1.1KB | +4.2KB | +| net11.0 | 10.0KB | 30.4KB | +20.4KB | +16.5KB | | +1.6KB | +4.2KB | diff --git a/src/Polyfill/ReadOnlySequenceStream.cs b/src/Polyfill/ReadOnlySequenceStream.cs index 04057be0..4e3d97e8 100644 --- a/src/Polyfill/ReadOnlySequenceStream.cs +++ b/src/Polyfill/ReadOnlySequenceStream.cs @@ -16,7 +16,7 @@ namespace System.Buffers; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -35,10 +35,8 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - // Incremental cursor into the sequence's segments, kept in sync with the absolute position. - // Advancing from this cursor avoids re-walking the segment list from the start on every read. - SequencePosition cursor; - long position; + // Cursor into the sequence segments. Reads advance it forward; it can never be moved back. + SequencePosition position; bool disposed; /// @@ -48,47 +46,33 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + // Intentionally non-seekable, matching the BCL (dotnet/runtime#132023): backward positioning would + // have to walk the segments again from the start, making repeated seeks worst case O(N), and segment + // boundaries can be indirectly controlled by an untrusted network client through packet framing. + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + // Length and Position stay unsupported to match the standard contract for streams where CanSeek is + // false, even though the underlying sequence could supply the length cheaply. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); - /// + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// @@ -97,12 +81,7 @@ 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(cursor); + var remaining = sequence.Slice(position); var toRead = (int)Math.Min(remaining.Length, count); if (toRead <= 0) { @@ -110,8 +89,7 @@ public override int Read(byte[] buffer, int offset, int count) } remaining.Slice(0, toRead).CopyTo(buffer.AsSpan(offset, toRead)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } @@ -120,14 +98,14 @@ public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } @@ -145,76 +123,33 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - - MoveTo(newPosition); - return position; - } - - // Repositions the segment cursor to the given absolute position, advancing forward from the - // current cursor when possible and only walking from the start for backward jumps. - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - // Stream.CopyTo(Stream, int) only became virtual in netcoreapp2.1/netstandard2.1. On older - // targets it cannot be overridden, so the base implementation (which routes through the - // cursor-based Read above) is used instead. + // Writing a segment straight through needs Stream.Write(ReadOnlySpan). On older targets that + // overload does not exist, so the base implementation (which routes through the cursor-based Read + // above) is used instead. /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } #endif @@ -229,23 +164,23 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) @@ -321,7 +256,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } diff --git a/src/Split/net10.0/ReadOnlySequenceStream.cs b/src/Split/net10.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net10.0/ReadOnlySequenceStream.cs +++ b/src/Split/net10.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net461/ReadOnlySequenceStream.cs b/src/Split/net461/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net461/ReadOnlySequenceStream.cs +++ b/src/Split/net461/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net462/ReadOnlySequenceStream.cs b/src/Split/net462/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net462/ReadOnlySequenceStream.cs +++ b/src/Split/net462/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net47/ReadOnlySequenceStream.cs b/src/Split/net47/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net47/ReadOnlySequenceStream.cs +++ b/src/Split/net47/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net471/ReadOnlySequenceStream.cs b/src/Split/net471/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net471/ReadOnlySequenceStream.cs +++ b/src/Split/net471/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net472/ReadOnlySequenceStream.cs b/src/Split/net472/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net472/ReadOnlySequenceStream.cs +++ b/src/Split/net472/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net48/ReadOnlySequenceStream.cs b/src/Split/net48/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net48/ReadOnlySequenceStream.cs +++ b/src/Split/net48/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net481/ReadOnlySequenceStream.cs b/src/Split/net481/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/net481/ReadOnlySequenceStream.cs +++ b/src/Split/net481/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net5.0/ReadOnlySequenceStream.cs b/src/Split/net5.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net5.0/ReadOnlySequenceStream.cs +++ b/src/Split/net5.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net6.0/ReadOnlySequenceStream.cs b/src/Split/net6.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net6.0/ReadOnlySequenceStream.cs +++ b/src/Split/net6.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net7.0/ReadOnlySequenceStream.cs b/src/Split/net7.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net7.0/ReadOnlySequenceStream.cs +++ b/src/Split/net7.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net8.0/ReadOnlySequenceStream.cs b/src/Split/net8.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net8.0/ReadOnlySequenceStream.cs +++ b/src/Split/net8.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/net9.0/ReadOnlySequenceStream.cs b/src/Split/net9.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/net9.0/ReadOnlySequenceStream.cs +++ b/src/Split/net9.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs b/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs +++ b/src/Split/netcoreapp2.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netcoreapp2.1/ReadOnlySequenceStream.cs b/src/Split/netcoreapp2.1/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/netcoreapp2.1/ReadOnlySequenceStream.cs +++ b/src/Split/netcoreapp2.1/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netcoreapp2.2/ReadOnlySequenceStream.cs b/src/Split/netcoreapp2.2/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/netcoreapp2.2/ReadOnlySequenceStream.cs +++ b/src/Split/netcoreapp2.2/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netcoreapp3.0/ReadOnlySequenceStream.cs b/src/Split/netcoreapp3.0/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/netcoreapp3.0/ReadOnlySequenceStream.cs +++ b/src/Split/netcoreapp3.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netcoreapp3.1/ReadOnlySequenceStream.cs b/src/Split/netcoreapp3.1/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/netcoreapp3.1/ReadOnlySequenceStream.cs +++ b/src/Split/netcoreapp3.1/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netstandard2.0/ReadOnlySequenceStream.cs b/src/Split/netstandard2.0/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/netstandard2.0/ReadOnlySequenceStream.cs +++ b/src/Split/netstandard2.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/netstandard2.1/ReadOnlySequenceStream.cs b/src/Split/netstandard2.1/ReadOnlySequenceStream.cs index c8adc7a4..4d37bbac 100644 --- a/src/Split/netstandard2.1/ReadOnlySequenceStream.cs +++ b/src/Split/netstandard2.1/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,60 +94,25 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override void CopyTo(Stream destination, int bufferSize) { GuardCopyTo(destination, bufferSize); ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return; } - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { destination.Write(segment.Span); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) @@ -182,20 +123,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -238,7 +179,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Split/uap10.0/ReadOnlySequenceStream.cs b/src/Split/uap10.0/ReadOnlySequenceStream.cs index ee49714e..19276a20 100644 --- a/src/Split/uap10.0/ReadOnlySequenceStream.cs +++ b/src/Split/uap10.0/ReadOnlySequenceStream.cs @@ -11,7 +11,7 @@ namespace System.Buffers; using System.Threading; using System.Threading.Tasks; /// -/// Provides a seekable, read-only over a . +/// Provides a read-only, non-seekable for reading from a . /// /// /// The underlying sequence is not copied; reads are served directly from its segments. @@ -29,8 +29,7 @@ sealed class ReadOnlySequenceStream : Stream { ReadOnlySequence sequence; - SequencePosition cursor; - long position; + SequencePosition position; bool disposed; /// /// Initializes a new instance of the class over the specified . @@ -38,73 +37,50 @@ sealed class ReadOnlySequenceStream : public ReadOnlySequenceStream(ReadOnlySequence source) { sequence = source; - cursor = source.Start; - position = 0; + position = source.Start; } /// public override bool CanRead => !disposed; - /// - public override bool CanSeek => !disposed; + /// Gets a value indicating whether the stream supports seeking. Always . + public override bool CanSeek => false; /// public override bool CanWrite => false; - /// - public override long Length - { - get - { - ThrowIfDisposed(); - return sequence.Length; - } - } - /// + /// Gets the length of the stream. Not supported; always throws a . + /// In all cases. + public override long Length => throw new NotSupportedException("Stream does not support seeking."); + /// Gets or sets the position within the stream. Not supported; always throws a . + /// In all cases. public override long Position { - get - { - ThrowIfDisposed(); - return position; - } - set - { - ThrowIfDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - MoveTo(value); - } + get => throw new NotSupportedException("Stream does not support seeking."); + set => throw new NotSupportedException("Stream does not support seeking."); } /// 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(cursor); + 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)); - cursor = sequence.GetPosition(toRead, cursor); - position += toRead; + position = sequence.GetPosition(toRead, position); return toRead; } /// public override int ReadByte() { ThrowIfDisposed(); - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return -1; } - var result = sequence.Slice(cursor, 1).First.Span[0]; - cursor = sequence.GetPosition(1, cursor); - position++; + var result = remaining.Slice(0, 1).First.Span[0]; + position = sequence.GetPosition(1, position); return result; } /// @@ -118,45 +94,10 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel } return Task.FromResult(Read(buffer, offset, count)); } - /// - 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)) - }; - if (offset > long.MaxValue - basePosition) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - var newPosition = basePosition + offset; - if (newPosition < 0) - { - throw new IOException("An attempt was made to move the position before the beginning of the stream."); - } - MoveTo(newPosition); - return position; - } - void MoveTo(long value) - { - if (value >= sequence.Length) - { - cursor = sequence.End; - } - else if (value >= position) - { - cursor = sequence.GetPosition(value - position, cursor); - } - else - { - cursor = sequence.GetPosition(value, sequence.Start); - } - position = value; - } + /// Sets the position within the stream. Not supported; always throws a . + /// In all cases. + public override long Seek(long offset, SeekOrigin origin) => + throw new NotSupportedException("Stream does not support seeking."); /// public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { @@ -166,20 +107,20 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio { return Task.FromCanceled(cancellationToken); } - if (position >= sequence.Length) + var remaining = sequence.Slice(position); + if (remaining.IsEmpty) { return Task.CompletedTask; } - return CopyToAsyncCore(destination, cancellationToken); + return CopyToAsyncCore(remaining, destination, cancellationToken); } - async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken) + async Task CopyToAsyncCore(ReadOnlySequence remaining, Stream destination, CancellationToken cancellationToken) { - foreach (var segment in sequence.Slice(cursor)) + foreach (var segment in remaining) { await WriteSegmentAsync(destination, segment, cancellationToken).ConfigureAwait(false); } - cursor = sequence.End; - position = sequence.Length; + position = sequence.End; } static Task WriteSegmentAsync(Stream destination, ReadOnlyMemory segment, CancellationToken cancellationToken) { @@ -237,7 +178,7 @@ protected override void Dispose(bool disposing) { disposed = true; sequence = default; - cursor = default; + position = default; base.Dispose(disposing); } void ThrowIfDisposed() diff --git a/src/Tests/PolyfillTests_ReadOnlySequenceStream.cs b/src/Tests/PolyfillTests_ReadOnlySequenceStream.cs index d3c1c7f5..5b49eacf 100644 --- a/src/Tests/PolyfillTests_ReadOnlySequenceStream.cs +++ b/src/Tests/PolyfillTests_ReadOnlySequenceStream.cs @@ -10,9 +10,8 @@ public async Task ReadOnlySequenceStream_Capabilities() using var stream = new ReadOnlySequenceStream(new ReadOnlySequence(new byte[] { 1, 2, 3 })); await Assert.That(stream.CanRead).IsTrue(); - await Assert.That(stream.CanSeek).IsTrue(); + await Assert.That(stream.CanSeek).IsFalse(); await Assert.That(stream.CanWrite).IsFalse(); - await Assert.That(stream.Length).IsEqualTo(3L); } [Test] @@ -31,8 +30,6 @@ public async Task ReadOnlySequenceStream_MultiSegment_ReadsAcrossSegments() var sequence = CreateMultiSegment(new byte[] { 1, 2, 3 }, new byte[] { 4, 5 }, new byte[] { 6, 7, 8, 9 }); using var stream = new ReadOnlySequenceStream(sequence); - await Assert.That(stream.Length).IsEqualTo(9L); - // Read in small chunks so reads straddle segment boundaries. using var accumulator = new MemoryStream(); var buffer = new byte[2]; @@ -58,65 +55,33 @@ public async Task ReadOnlySequenceStream_ReadByte_AcrossSegments() } [Test] - public async Task ReadOnlySequenceStream_Seek() + public async Task ReadOnlySequenceStream_SeekingThrows() { - using var stream = new ReadOnlySequenceStream(new ReadOnlySequence(new byte[] { 1, 2, 3, 4, 5 })); - - stream.Seek(3, SeekOrigin.Begin); - await Assert.That(stream.Position).IsEqualTo(3L); - await Assert.That(stream.ReadByte()).IsEqualTo(4); - - stream.Seek(-2, SeekOrigin.Current); - await Assert.That(stream.ReadByte()).IsEqualTo(3); + using var stream = new ReadOnlySequenceStream(new ReadOnlySequence(new byte[] { 1, 2, 3 })); - stream.Seek(-1, SeekOrigin.End); - await Assert.That(stream.ReadByte()).IsEqualTo(5); + await Assert.That(() => stream.Length).Throws(); + await Assert.That(() => stream.Position).Throws(); + await Assert.That(() => stream.Position = 1).Throws(); + await Assert.That(() => stream.Seek(0, SeekOrigin.Begin)).Throws(); + await Assert.That(() => stream.Seek(1, SeekOrigin.Current)).Throws(); + await Assert.That(() => stream.Seek(0, SeekOrigin.End)).Throws(); } [Test] - public async Task ReadOnlySequenceStream_SeekAcrossSegments_RepositionsCursor() + public async Task ReadOnlySequenceStream_ReadsAreForwardOnly() { - // bytes by index: 0..8 -> 1,2,3,4,5,6,7,8,9 across three segments. - var sequence = CreateMultiSegment(new byte[] { 1, 2, 3 }, new byte[] { 4, 5 }, new byte[] { 6, 7, 8, 9 }); + var sequence = CreateMultiSegment(new byte[] { 1, 2, 3 }, new byte[] { 4, 5 }); using var stream = new ReadOnlySequenceStream(sequence); - // Forward from the start into the third segment. - stream.Seek(6, SeekOrigin.Begin); - await Assert.That(stream.Position).IsEqualTo(6L); - await Assert.That(stream.ReadByte()).IsEqualTo(7); - - // Backward into the first segment (walk-from-start branch). - stream.Position = 1; + // A failed seek attempt must not disturb the cursor. + await Assert.That(stream.ReadByte()).IsEqualTo(1); + await Assert.That(() => stream.Seek(0, SeekOrigin.Begin)).Throws(); await Assert.That(stream.ReadByte()).IsEqualTo(2); - // Forward relative to the current cursor into the second segment. - stream.Seek(2, SeekOrigin.Current); - await Assert.That(stream.ReadByte()).IsEqualTo(5); - - // Clamp to the end via End origin. - stream.Seek(0, SeekOrigin.End); - await Assert.That(stream.Position).IsEqualTo(9L); - await Assert.That(stream.ReadByte()).IsEqualTo(-1); - } - - [Test] - public async Task ReadOnlySequenceStream_SeekBeyondLength_ReadsZero() - { - using var stream = new ReadOnlySequenceStream(new ReadOnlySequence(new byte[] { 1, 2, 3 })); - - stream.Position = 10; - - await Assert.That(stream.Position).IsEqualTo(10L); - await Assert.That(stream.ReadByte()).IsEqualTo(-1); - await Assert.That(stream.Read(new byte[4], 0, 4)).IsEqualTo(0); - } - - [Test] - public async Task ReadOnlySequenceStream_SeekBeforeBeginThrows() - { - using var stream = new ReadOnlySequenceStream(new ReadOnlySequence(new byte[] { 1, 2, 3 })); - - await Assert.That(() => stream.Seek(-1, SeekOrigin.Begin)).Throws(); + var rest = new byte[4]; + await Assert.That(stream.Read(rest, 0, 4)).IsEqualTo(3); + await Assert.That(rest).IsEquivalentTo(new byte[] { 3, 4, 5, 0 }); + await Assert.That(stream.Read(rest, 0, 4)).IsEqualTo(0); } [Test] @@ -145,8 +110,8 @@ public async Task ReadOnlySequenceStream_Empty() { using var stream = new ReadOnlySequenceStream(ReadOnlySequence.Empty); - await Assert.That(stream.Length).IsEqualTo(0L); await Assert.That(stream.Read(new byte[4], 0, 4)).IsEqualTo(0); + await Assert.That(stream.ReadByte()).IsEqualTo(-1); } [Test] @@ -159,7 +124,7 @@ public async Task ReadOnlySequenceStream_CopyTo_MultiSegment() stream.CopyTo(target); await Assert.That(target.ToArray()).IsEquivalentTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - await Assert.That(stream.Position).IsEqualTo(9L); + await Assert.That(stream.Read(new byte[1], 0, 1)).IsEqualTo(0); } [Test] @@ -175,7 +140,7 @@ public async Task ReadOnlySequenceStream_CopyTo_AfterPartialRead_CopiesRemainder stream.CopyTo(target); await Assert.That(target.ToArray()).IsEquivalentTo(new byte[] { 3, 4, 5, 6 }); - await Assert.That(stream.Position).IsEqualTo(6L); + await Assert.That(stream.Read(new byte[1], 0, 1)).IsEqualTo(0); } [Test] @@ -188,7 +153,7 @@ public async Task ReadOnlySequenceStream_CopyToAsync_MultiSegment() await stream.CopyToAsync(target); await Assert.That(target.ToArray()).IsEquivalentTo(new byte[] { 1, 2, 3, 4, 5 }); - await Assert.That(stream.Position).IsEqualTo(5L); + await Assert.That(stream.Read(new byte[1], 0, 1)).IsEqualTo(0); } static ReadOnlySequence CreateMultiSegment(params byte[][] parts)