From 8f9d4d428d91d9b6ad2ce33eb1712327d986a6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viviana=20Due=C3=B1as=20Chavez?= Date: Fri, 26 Jun 2026 12:37:53 -0700 Subject: [PATCH] Alternative shape: derive ReadOnlyMemoryStream / WritableMemoryStream from Stream --- .../src/System/IO/MemoryStream.cs | 10 +- .../src/System/IO/ReadOnlyMemoryStream.cs | 126 +++++++++++++++--- .../src/System/IO/WritableMemoryStream.cs | 106 +++++++++++++-- .../System.Runtime/ref/System.Runtime.cs | 44 ++++-- 4 files changed, 239 insertions(+), 47 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs index 7f6001364fcc85..c32406553e1088 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -22,17 +22,17 @@ public class MemoryStream : Stream { private byte[] _buffer; // Either allocated internally or externally. private readonly int _origin; // For user-provided arrays, start at this origin - private protected int _position; // read/write head. - private protected int _length; // Number of bytes within the memory stream + private int _position; // read/write head. + private int _length; // Number of bytes within the memory stream private int _capacity; // length of usable portion of buffer for stream // Note that _capacity == _buffer.Length for non-user-provided byte[]'s private bool _expandable; // User-provided buffers aren't expandable. - private protected bool _writable; // Can user write to this stream? + private bool _writable; // Can user write to this stream? private readonly bool _exposable; // Whether the array can be returned to the user. - private protected bool _isOpen; // Is this stream open or closed? + private bool _isOpen; // Is this stream open or closed? - private protected CachedCompletedInt32Task _lastReadTask; // The last successful task returned from ReadAsync + private CachedCompletedInt32Task _lastReadTask; // The last successful task returned from ReadAsync public MemoryStream() : this(0) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs index a3dd29fc9438a3..bfa15300cb994c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/ReadOnlyMemoryStream.cs @@ -7,49 +7,140 @@ namespace System.IO { /// - /// Provides a seekable, read-only over a . + /// Provides a seekable, read-only over a . /// /// - /// The stream cannot be written to. always returns . - /// throws and returns . + /// The stream cannot be written to. always returns . + /// throws and returns . /// - public sealed class ReadOnlyMemoryStream : MemoryStream + public sealed class ReadOnlyMemoryStream : Stream { private ReadOnlyMemory _memory; + private int _position; + private bool _isOpen; + private CachedCompletedInt32Task _lastReadTask; /// /// Initializes a new instance of the class over the specified . /// /// The to wrap. - public ReadOnlyMemoryStream(ReadOnlyMemory source) : base() + public ReadOnlyMemoryStream(ReadOnlyMemory source) { - _writable = false; _memory = source; - _length = source.Length; + _isOpen = true; } /// - public override int Capacity + public override bool CanRead => _isOpen; + + /// + public override bool CanSeek => _isOpen; + + /// + public override bool CanWrite => false; + + /// + public override long Length { get { EnsureNotClosed(); return _memory.Length; } - set => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable); } /// - public override byte[] GetBuffer() => - throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); + public override long Position + { + get + { + EnsureNotClosed(); + return _position; + } + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); + EnsureNotClosed(); + _position = (int)value; + } + } + + /// + public override long Seek(long offset, SeekOrigin loc) + { + EnsureNotClosed(); + + ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, int.MaxValue); + + long target = loc switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _memory.Length + offset, + _ => throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(loc)), + }; + + if (target < 0) + throw new IOException(SR.IO_SeekBeforeBegin); + ArgumentOutOfRangeException.ThrowIfGreaterThan(target, int.MaxValue, nameof(offset)); + + _position = (int)target; + return _position; + } + + /// + public override void Flush() { } + + /// + public override Task FlushAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : Task.CompletedTask; /// - public override bool TryGetBuffer(out ArraySegment buffer) + public override void SetLength(long value) => + throw new NotSupportedException(SR.NotSupported_UnwritableStream); + + /// Gets the size of the underlying buffer. + public int Capacity + { + get + { + EnsureNotClosed(); + return _memory.Length; + } + } + + /// Always throws; the underlying buffer is not exposed. + /// Always thrown. + public byte[] GetBuffer() => + throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); + + /// Always returns ; the underlying buffer is not exposed. + /// When this method returns, contains the default value of . + /// . + public bool TryGetBuffer(out ArraySegment buffer) { buffer = default; return false; } + /// + public override void Write(byte[] buffer, int offset, int count) + { + ValidateBufferArguments(buffer, offset, count); + throw new NotSupportedException(SR.NotSupported_UnwritableStream); + } + + /// + public override void Write(ReadOnlySpan buffer) => + throw new NotSupportedException(SR.NotSupported_UnwritableStream); + + /// + public override void WriteByte(byte value) => + throw new NotSupportedException(SR.NotSupported_UnwritableStream); + /// public override int ReadByte() { @@ -156,8 +247,9 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio return Task.CompletedTask; } - /// - public override byte[] ToArray() + /// Writes the stream contents to a byte array, regardless of the . + /// A new byte array containing a copy of the stream's contents. + public byte[] ToArray() { EnsureNotClosed(); if (_memory.Length == 0) @@ -170,8 +262,9 @@ public override byte[] ToArray() return copy; } - /// - public override void WriteTo(Stream stream) + /// Writes the entire contents of this stream to another stream. + /// The destination stream. + public void WriteTo(Stream stream) { ArgumentNullException.ThrowIfNull(stream); EnsureNotClosed(); @@ -183,6 +276,7 @@ public override void WriteTo(Stream stream) protected override void Dispose(bool disposing) { _memory = default; + _isOpen = false; base.Dispose(disposing); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs index 2b6284f7746f20..786c4756242589 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/WritableMemoryStream.cs @@ -7,42 +7,117 @@ namespace System.IO { /// - /// Provides a seekable, writable over a with fixed capacity. + /// Provides a seekable, writable over a with fixed capacity. /// /// /// The stream cannot expand beyond the initial memory capacity. - /// throws and returns . + /// throws and returns . /// - public sealed class WritableMemoryStream : MemoryStream + public sealed class WritableMemoryStream : Stream { private Memory _memory; + private int _position; + private int _length; + private bool _isOpen; + private CachedCompletedInt32Task _lastReadTask; /// /// Initializes a new instance of the class over the specified . /// /// The to wrap. - public WritableMemoryStream(Memory buffer) : base() + public WritableMemoryStream(Memory buffer) { _memory = buffer; + _isOpen = true; } /// - public override int Capacity + public override bool CanRead => _isOpen; + + /// + public override bool CanSeek => _isOpen; + + /// + public override bool CanWrite => _isOpen; + + /// + public override long Length { get { EnsureNotClosed(); - return _memory.Length; + return _length; } - set => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable); } /// - public override byte[] GetBuffer() => - throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); + public override long Position + { + get + { + EnsureNotClosed(); + return _position; + } + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + ArgumentOutOfRangeException.ThrowIfGreaterThan(value, int.MaxValue); + EnsureNotClosed(); + _position = (int)value; + } + } + + /// + public override long Seek(long offset, SeekOrigin loc) + { + EnsureNotClosed(); + + ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, int.MaxValue); + + long target = loc switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _position + offset, + SeekOrigin.End => _length + offset, + _ => throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(loc)), + }; + + if (target < 0) + throw new IOException(SR.IO_SeekBeforeBegin); + ArgumentOutOfRangeException.ThrowIfGreaterThan(target, int.MaxValue, nameof(offset)); + + _position = (int)target; + return _position; + } /// - public override bool TryGetBuffer(out ArraySegment buffer) + public override void Flush() { } + + /// + public override Task FlushAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : Task.CompletedTask; + + /// Gets the size of the underlying buffer. + public int Capacity + { + get + { + EnsureNotClosed(); + return _memory.Length; + } + } + + /// Always throws; the underlying buffer is not exposed. + /// Always thrown. + public byte[] GetBuffer() => + throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); + + /// Always returns ; the underlying buffer is not exposed. + /// When this method returns, contains the default value of . + /// . + public bool TryGetBuffer(out ArraySegment buffer) { buffer = default; return false; @@ -238,8 +313,9 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo /// public override void SetLength(long value) => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable); - /// - public override byte[] ToArray() + /// Writes the stream contents to a byte array, regardless of the . + /// A new byte array containing a copy of the written contents. + public byte[] ToArray() { EnsureNotClosed(); if (_length == 0) @@ -252,8 +328,9 @@ public override byte[] ToArray() return copy; } - /// - public override void WriteTo(Stream stream) + /// Writes the stream contents to another stream. + /// The destination stream. + public void WriteTo(Stream stream) { ArgumentNullException.ThrowIfNull(stream); EnsureNotClosed(); @@ -265,6 +342,7 @@ public override void WriteTo(Stream stream) protected override void Dispose(bool disposing) { _memory = default; + _isOpen = false; base.Dispose(disposing); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7e7ae5dbe9f49b..812fcc56d65c08 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11053,45 +11053,65 @@ public override void Write(System.ReadOnlySpan buffer) { } public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public sealed partial class ReadOnlyMemoryStream : System.IO.MemoryStream + public sealed partial class ReadOnlyMemoryStream : System.IO.Stream { public ReadOnlyMemoryStream(System.ReadOnlyMemory source) { } - public override int Capacity { get { throw null; } set { } } + public int Capacity { get { throw null; } } + public override bool CanRead { get { throw null; } } + public override bool CanSeek { get { throw null; } } + public override bool CanWrite { get { throw null; } } + public override long Length { get { throw null; } } + public override long Position { get { throw null; } set { } } + public override long Seek(long offset, System.IO.SeekOrigin loc) { throw null; } + public override void SetLength(long value) { } + public override void Flush() { } + public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } - public override byte[] GetBuffer() { throw null; } + public byte[] GetBuffer() { throw null; } public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } - public override byte[] ToArray() { throw null; } - public override bool TryGetBuffer(out System.ArraySegment buffer) { throw null; } - public override void WriteTo(System.IO.Stream stream) { } + public override void Write(byte[] buffer, int offset, int count) { } + public override void Write(System.ReadOnlySpan buffer) { } + public override void WriteByte(byte value) { } + public byte[] ToArray() { throw null; } + public bool TryGetBuffer(out System.ArraySegment buffer) { throw null; } + public void WriteTo(System.IO.Stream stream) { } } - public sealed partial class WritableMemoryStream : System.IO.MemoryStream + public sealed partial class WritableMemoryStream : System.IO.Stream { public WritableMemoryStream(System.Memory buffer) { } - public override int Capacity { get { throw null; } set { } } + public int Capacity { get { throw null; } } + public override bool CanRead { get { throw null; } } + public override bool CanSeek { get { throw null; } } + public override bool CanWrite { get { throw null; } } + public override long Length { get { throw null; } } + public override long Position { get { throw null; } set { } } + public override long Seek(long offset, System.IO.SeekOrigin loc) { throw null; } + public override void Flush() { } + public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } public override System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } protected override void Dispose(bool disposing) { } - public override byte[] GetBuffer() { throw null; } + public byte[] GetBuffer() { throw null; } public override int Read(byte[] buffer, int offset, int count) { throw null; } public override int Read(System.Span buffer) { throw null; } public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override int ReadByte() { throw null; } public override void SetLength(long value) { } - public override byte[] ToArray() { throw null; } - public override bool TryGetBuffer(out System.ArraySegment buffer) { throw null; } + public byte[] ToArray() { throw null; } + public bool TryGetBuffer(out System.ArraySegment buffer) { throw null; } public override void Write(byte[] buffer, int offset, int count) { } public override void Write(System.ReadOnlySpan buffer) { } public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override void WriteByte(byte value) { } - public override void WriteTo(System.IO.Stream stream) { } + public void WriteTo(System.IO.Stream stream) { } } public partial class StringWriter : System.IO.TextWriter {