Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,140 @@
namespace System.IO
{
/// <summary>
/// Provides a seekable, read-only <see cref="MemoryStream"/> over a <see cref="ReadOnlyMemory{Byte}"/>.
/// Provides a seekable, read-only <see cref="Stream"/> over a <see cref="ReadOnlyMemory{Byte}"/>.
/// </summary>
/// <remarks>
/// <para>The stream cannot be written to. <see cref="MemoryStream.CanWrite"/> always returns <see langword="false"/>.</para>
/// <para><see cref="MemoryStream.GetBuffer"/> throws and <see cref="MemoryStream.TryGetBuffer"/> returns <see langword="false"/>.</para>
/// <para>The stream cannot be written to. <see cref="CanWrite"/> always returns <see langword="false"/>.</para>
/// <para><see cref="GetBuffer"/> throws and <see cref="TryGetBuffer"/> returns <see langword="false"/>.</para>
/// </remarks>
public sealed class ReadOnlyMemoryStream : MemoryStream
public sealed class ReadOnlyMemoryStream : Stream
{
private ReadOnlyMemory<byte> _memory;
private int _position;
private bool _isOpen;
private CachedCompletedInt32Task _lastReadTask;

/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemoryStream"/> class over the specified <see cref="ReadOnlyMemory{Byte}"/>.
/// </summary>
/// <param name="source">The <see cref="ReadOnlyMemory{Byte}"/> to wrap.</param>
public ReadOnlyMemoryStream(ReadOnlyMemory<byte> source) : base()
public ReadOnlyMemoryStream(ReadOnlyMemory<byte> source)
{
_writable = false;
_memory = source;
_length = source.Length;
_isOpen = true;
}

/// <inheritdoc/>
public override int Capacity
public override bool CanRead => _isOpen;

/// <inheritdoc/>
public override bool CanSeek => _isOpen;

/// <inheritdoc/>
public override bool CanWrite => false;

/// <inheritdoc/>
public override long Length
{
get
{
EnsureNotClosed();
return _memory.Length;
}
set => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable);
}

/// <inheritdoc/>
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;
}
Comment on lines +60 to +66
}

/// <inheritdoc/>
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;
}

/// <inheritdoc/>
public override void Flush() { }

/// <inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested
? Task.FromCanceled(cancellationToken)
: Task.CompletedTask;

/// <inheritdoc/>
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
public override void SetLength(long value) =>
throw new NotSupportedException(SR.NotSupported_UnwritableStream);
Comment on lines 101 to +103

/// <summary>Gets the size of the underlying buffer.</summary>
public int Capacity
{
get
{
EnsureNotClosed();
return _memory.Length;
}
}

/// <summary>Always throws; the underlying buffer is not exposed.</summary>
/// <exception cref="UnauthorizedAccessException">Always thrown.</exception>
public byte[] GetBuffer() =>
throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer);

/// <summary>Always returns <see langword="false"/>; the underlying buffer is not exposed.</summary>
/// <param name="buffer">When this method returns, contains the default value of <see cref="ArraySegment{Byte}"/>.</param>
/// <returns><see langword="false"/>.</returns>
public bool TryGetBuffer(out ArraySegment<byte> buffer)
{
buffer = default;
return false;
}

/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
throw new NotSupportedException(SR.NotSupported_UnwritableStream);
}
Comment on lines +129 to +134

/// <inheritdoc/>
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 +137 to +142

/// <inheritdoc/>
public override int ReadByte()
{
Expand Down Expand Up @@ -156,8 +247,9 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio
return Task.CompletedTask;
}

/// <inheritdoc/>
public override byte[] ToArray()
/// <summary>Writes the stream contents to a byte array, regardless of the <see cref="Position"/>.</summary>
/// <returns>A new byte array containing a copy of the stream's contents.</returns>
public byte[] ToArray()
{
EnsureNotClosed();
if (_memory.Length == 0)
Expand All @@ -170,8 +262,9 @@ public override byte[] ToArray()
return copy;
}

/// <inheritdoc/>
public override void WriteTo(Stream stream)
/// <summary>Writes the entire contents of this stream to another stream.</summary>
/// <param name="stream">The destination stream.</param>
public void WriteTo(Stream stream)
{
ArgumentNullException.ThrowIfNull(stream);
EnsureNotClosed();
Expand All @@ -183,6 +276,7 @@ public override void WriteTo(Stream stream)
protected override void Dispose(bool disposing)
{
_memory = default;
_isOpen = false;
base.Dispose(disposing);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,117 @@
namespace System.IO
{
/// <summary>
/// Provides a seekable, writable <see cref="MemoryStream"/> over a <see cref="Memory{Byte}"/> with fixed capacity.
/// Provides a seekable, writable <see cref="Stream"/> over a <see cref="Memory{Byte}"/> with fixed capacity.
/// </summary>
/// <remarks>
/// <para>The stream cannot expand beyond the initial memory capacity.</para>
/// <para><see cref="MemoryStream.GetBuffer"/> throws and <see cref="MemoryStream.TryGetBuffer"/> returns <see langword="false"/>.</para>
/// <para><see cref="GetBuffer"/> throws and <see cref="TryGetBuffer"/> returns <see langword="false"/>.</para>
/// </remarks>
public sealed class WritableMemoryStream : MemoryStream
public sealed class WritableMemoryStream : Stream
{
private Memory<byte> _memory;
private int _position;
private int _length;
private bool _isOpen;
private CachedCompletedInt32Task _lastReadTask;

/// <summary>
/// Initializes a new instance of the <see cref="WritableMemoryStream"/> class over the specified <see cref="Memory{Byte}"/>.
/// </summary>
/// <param name="buffer">The <see cref="Memory{Byte}"/> to wrap.</param>
public WritableMemoryStream(Memory<byte> buffer) : base()
public WritableMemoryStream(Memory<byte> buffer)
{
_memory = buffer;
_isOpen = true;
}

/// <inheritdoc/>
public override int Capacity
public override bool CanRead => _isOpen;

/// <inheritdoc/>
public override bool CanSeek => _isOpen;

/// <inheritdoc/>
public override bool CanWrite => _isOpen;

/// <inheritdoc/>
public override long Length
{
get
{
EnsureNotClosed();
return _memory.Length;
return _length;
}
set => throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable);
}

/// <inheritdoc/>
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;
}
Comment on lines +61 to +67
}

/// <inheritdoc/>
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;
}

/// <inheritdoc/>
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
public override void Flush() { }

/// <inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested
? Task.FromCanceled(cancellationToken)
: Task.CompletedTask;

/// <summary>Gets the size of the underlying buffer.</summary>
public int Capacity
{
get
{
EnsureNotClosed();
return _memory.Length;
}
}

/// <summary>Always throws; the underlying buffer is not exposed.</summary>
/// <exception cref="UnauthorizedAccessException">Always thrown.</exception>
public byte[] GetBuffer() =>
throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer);

/// <summary>Always returns <see langword="false"/>; the underlying buffer is not exposed.</summary>
/// <param name="buffer">When this method returns, contains the default value of <see cref="ArraySegment{Byte}"/>.</param>
/// <returns><see langword="false"/>.</returns>
public bool TryGetBuffer(out ArraySegment<byte> buffer)
{
buffer = default;
return false;
Expand Down Expand Up @@ -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);

/// <inheritdoc/>
public override byte[] ToArray()
/// <summary>Writes the stream contents to a byte array, regardless of the <see cref="Position"/>.</summary>
/// <returns>A new byte array containing a copy of the written contents.</returns>
public byte[] ToArray()
{
EnsureNotClosed();
if (_length == 0)
Expand All @@ -252,8 +328,9 @@ public override byte[] ToArray()
return copy;
}

/// <inheritdoc/>
public override void WriteTo(Stream stream)
/// <summary>Writes the stream contents to another stream.</summary>
/// <param name="stream">The destination stream.</param>
public void WriteTo(Stream stream)
{
ArgumentNullException.ThrowIfNull(stream);
EnsureNotClosed();
Expand All @@ -265,6 +342,7 @@ public override void WriteTo(Stream stream)
protected override void Dispose(bool disposing)
{
_memory = default;
_isOpen = false;
base.Dispose(disposing);
}

Expand Down
Loading
Loading