[Obsolete-WIP] Alternative shape for ReadOnlyMemoryStream / WritableMemoryStream#129912
[Obsolete-WIP] Alternative shape for ReadOnlyMemoryStream / WritableMemoryStream#129912ViveliDuCh wants to merge 1 commit into
Conversation
|
Tagging subscribers to this area: @dotnet/area-system-io |
There was a problem hiding this comment.
Pull request overview
This PR prototypes an alternative implementation shape for the public System.IO.ReadOnlyMemoryStream and System.IO.WritableMemoryStream types by deriving them directly from Stream (rather than MemoryStream), with the goal of reducing per-instance size and avoiding inherited MemoryStream buffer-management surface that doesn’t apply to fixed Memory<byte> / ReadOnlyMemory<byte> wrappers.
Changes:
- Switch
ReadOnlyMemoryStream/WritableMemoryStreamfromMemoryStream-derived toStream-derived implementations, adding local state andStreamoverrides. - Revert
MemoryStreamfield visibility promotions (private protected→private) that were used to share state with the wrappers in theMemoryStream-derived shape. - Update the
System.Runtimeref assembly to reflect the prototype’s public API shape.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs | Reverts field visibility promotions that were only needed for the MemoryStream-derived wrapper hierarchy. |
| src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs | Implements ReadOnlyMemoryStream : Stream with local position/open state and relevant Stream overrides; keeps MemoryStream-like helper methods as non-overrides. |
| src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs | Implements WritableMemoryStream : Stream with local position/length/open state and relevant Stream overrides; keeps helper methods as non-overrides. |
| src/libraries/System.Runtime/ref/System.Runtime.cs | Updates public contract (base type and member set) to match the prototype Stream-derived shape. |
| public sealed partial class ReadOnlyMemoryStream : System.IO.Stream | ||
| { | ||
| public ReadOnlyMemoryStream(System.ReadOnlyMemory<byte> source) { } | ||
| public override int Capacity { get { throw null; } set { } } | ||
| public int Capacity { get { throw null; } } | ||
| public override bool CanRead { get { throw null; } } |
| set | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfNegative(value); | ||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); | ||
| EnsureNotClosed(); | ||
| _position = (int)value; | ||
| } |
| set | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfNegative(value); | ||
| ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); | ||
| EnsureNotClosed(); | ||
| _position = (int)value; | ||
| } |
|
Testing AI workflow, wrong targeting branch. Closing as duplicate of #129913 — superseded by the manually-opened PR against the correct upstream branch. |
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.