[Prototype] Alternative shape: derive ReadOnlyMemoryStream / WritableMemoryStream from Stream#129913
Open
ViveliDuCh wants to merge 1 commit into
Open
[Prototype] Alternative shape: derive ReadOnlyMemoryStream / WritableMemoryStream from Stream#129913ViveliDuCh wants to merge 1 commit into
ViveliDuCh wants to merge 1 commit into
Conversation
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-io |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prototypes an alternative implementation of ReadOnlyMemoryStream and WritableMemoryStream by deriving them from Stream (with local backing state) instead of MemoryStream, and reverts the private protected field promotions on MemoryStream that were previously added to support a MemoryStream-based wrapper shape.
Changes:
ReadOnlyMemoryStream/WritableMemoryStream: base type changed toStream, with added local state (_position,_lengthwhere applicable,_isOpen, cached read task) and explicitStreamoverrides (seek/position/length/flush/etc.).MemoryStream: revertsprivate protectedfield visibility back toprivate.ref/System.Runtime.cs: updates the public API contract to match the new base type and member declarations.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| src/libraries/System.Runtime/ref/System.Runtime.cs | Updates the public contract to reflect ReadOnlyMemoryStream / WritableMemoryStream : Stream and their members. |
| src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs | Re-implements ReadOnlyMemoryStream as a Stream-derived type with its own position/open-state and required overrides. |
| src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs | Re-implements WritableMemoryStream as a Stream-derived type with its own length/position/open-state and required overrides. |
| src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs | Reverts promoted field visibility (private protected → private). |
Comment on lines
+60
to
+66
| set | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfNegative(value); | ||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); | ||
| EnsureNotClosed(); | ||
| _position = (int)value; | ||
| } |
| { | ||
| EnsureNotClosed(); | ||
|
|
||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, int.MaxValue); |
Comment on lines
101
to
+103
| /// <inheritdoc/> | ||
| public override bool TryGetBuffer(out ArraySegment<byte> buffer) | ||
| public override void SetLength(long value) => | ||
| throw new NotSupportedException(SR.NotSupported_UnwritableStream); |
Comment on lines
+129
to
+134
| /// <inheritdoc/> | ||
| public override void Write(byte[] buffer, int offset, int count) | ||
| { | ||
| ValidateBufferArguments(buffer, offset, count); | ||
| throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||
| } |
Comment on lines
+137
to
+142
| public override void Write(ReadOnlySpan<byte> buffer) => | ||
| throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void WriteByte(byte value) => | ||
| throw new NotSupportedException(SR.NotSupported_UnwritableStream); |
Comment on lines
+61
to
+67
| set | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfNegative(value); | ||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); | ||
| EnsureNotClosed(); | ||
| _position = (int)value; | ||
| } |
| { | ||
| EnsureNotClosed(); | ||
|
|
||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, int.MaxValue); |
| @@ -238,8 +313,9 @@ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationTo | |||
| /// <inheritdoc/> | |||
| public override void SetLength(long value) => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable); | |||
Comment on lines
+11056
to
11057
| public sealed partial class ReadOnlyMemoryStream : System.IO.Stream | ||
| { |
Open
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up on #126669. Shows what
ReadOnlyMemoryStream/WritableMemoryStreamwould look like derived fromStreaminstead ofMemoryStream.Context of the trade-off (recap of the prior discussion)
Streambase.is MemoryStreamfor fast paths; deriving fromMemoryStreamlets the new wrappers participate in those optimizations and keeps naming consistency.MemoryStreamfor the initial shape, withprivate protectedfield promotions onMemoryStreamso the wrappers can share state — the shape that shipped.This PR is the other side of that fork, in case it's ever useful to compare.
What this prototype does
MemoryStream.csprivate protectedfield promotions added in #126669 — no consumer in the hierarchy under this shape.ReadOnlyMemoryStream.cs: MemoryStream→: Stream. Adds local backing state andStreamoverrides (CanRead/CanSeek/CanWrite/Length/Position/Seek/Flush/FlushAsync/SetLength).Capacity,GetBuffer,TryGetBuffer,ToArray,WriteToloseoverride;Capacitysetter dropped (only threw).Write/Write(ROSpan)/WriteBytethrowNotSupportedException.WritableMemoryStream.cs_lengthand the writable surface.ref/System.Runtime.csTrade-offs
For a
StreambaseMemoryStream's buffer-management state (_buffer,_origin,_capacity,_expandable,_exposable), so the inherited fields are dead weight on every instance.MemoryStreambase also brings inherited surface (WriteTo(byte[]), expandable-capacity semantics,GetBuffer/TryGetBufferover an internalbyte[], etc.) that doesn't really apply to a fixedMemory<byte>wrapper — under the current shape these have to be overridden to throw or no-op.Against a
Streambaseis MemoryStreamfast paths used by several ecosystem consumers (mono, EFCore, MSBuild and friends).…MemoryStream, which is a soft signal to users (and to the framework design "self-documenting names" guidance) that they areMemoryStreams. Renaming is on the table in principle, but if the names stay then a non-MemoryStreambase is a mismatch worth flagging.Testing
WritableMemoryStreamTestsReadOnlyMemoryStreamTestsWritableMemoryStreamConformanceTestsReadOnlyMemoryStreamConformanceTestsSystem.IO.TestsSystem.IO.UnmanagedMemoryStream.TestsSeekkeepsMemoryStream'sIOException(SR.IO_SeekBeforeBegin)on negative target to preserve the existing conformance contract.Note
This PR description was drafted with the assistance of GitHub Copilot.