-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Derive from Stream in ReadOnlyMemoryStream and WritableMemoryStream #130744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0e0efa6
25d62ee
421fb0a
e82b98a
401200e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,49 +7,78 @@ | |||||||||||||||||||||||||||||||||||
| 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> | ||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||
| public sealed class ReadOnlyMemoryStream : MemoryStream | ||||||||||||||||||||||||||||||||||||
|
tannergooding marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| public sealed class ReadOnlyMemoryStream : Stream | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| private ReadOnlyMemory<byte> _memory; | ||||||||||||||||||||||||||||||||||||
| private int _position; | ||||||||||||||||||||||||||||||||||||
| private bool _isOpen; | ||||||||||||||||||||||||||||||||||||
| private CachedCompletedInt32Task _lastReadTask; // The last successful task returned from ReadAsync | ||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. subjective: I am not a big fan of this performance optimization. @jakobbotsch what some of the BCL Streams do is that they keep a cached runtime/src/libraries/Common/src/System/Threading/Tasks/CachedCompletedInt32Task.cs Lines 22 to 33 in 883277e
So for example, when we open a large file and keep reading the contents using the buffer of the same size, we avoid allocating for all reads except of the first one (it initializes the cache) and the last one (assuming the number of bytes in @jakobbotsch do we still need such a micro optimization with runtime async enabled?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, this pattern is actually quite detrimental to performance. Runtime async recognizes Similarly bad is stuff like this: runtime/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs Lines 285 to 289 in e5fc966
This forces |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <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); | ||||||||||||||||||||||||||||||||||||
| EnsureNotClosed(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if (value > int.MaxValue) | ||||||||||||||||||||||||||||||||||||
|
tannergooding marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| throw new ArgumentOutOfRangeException(nameof(value), SR.Format(SR.ArgumentOutOfRange_StreamPosition, int.MaxValue)); | ||||||||||||||||||||||||||||||||||||
|
tannergooding marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| _position = (int)value; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override bool TryGetBuffer(out ArraySegment<byte> buffer) | ||||||||||||||||||||||||||||||||||||
| public override void Flush() | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| buffer = default; | ||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override Task FlushAsync(CancellationToken cancellationToken) => | ||||||||||||||||||||||||||||||||||||
| cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override int ReadByte() | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -121,6 +150,34 @@ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken | |||||||||||||||||||||||||||||||||||
| return new ValueTask<int>(Read(buffer.Span)); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override long Seek(long offset, SeekOrigin origin) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| EnsureNotClosed(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| int loc = origin switch | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| SeekOrigin.Begin => 0, | ||||||||||||||||||||||||||||||||||||
| SeekOrigin.Current => _position, | ||||||||||||||||||||||||||||||||||||
| SeekOrigin.End => _memory.Length, | ||||||||||||||||||||||||||||||||||||
| _ => throw new ArgumentException(SR.Argument_InvalidSeekOrigin) | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if (offset > int.MaxValue - loc) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| throw new ArgumentOutOfRangeException(nameof(offset), SR.Format(SR.ArgumentOutOfRange_StreamPosition, int.MaxValue)); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| int tempPosition = unchecked(loc + (int)offset); | ||||||||||||||||||||||||||||||||||||
| if (unchecked(loc + offset) < 0 || tempPosition < 0) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| throw new IOException(SR.IO_SeekBeforeBegin); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| _position = tempPosition; | ||||||||||||||||||||||||||||||||||||
| return _position; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override void CopyTo(Stream destination, int bufferSize) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -157,32 +214,29 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override byte[] ToArray() | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| EnsureNotClosed(); | ||||||||||||||||||||||||||||||||||||
| if (_memory.Length == 0) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| return Array.Empty<byte>(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| public override void SetLength(long value) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| byte[] copy = GC.AllocateUninitializedArray<byte>(_memory.Length); | ||||||||||||||||||||||||||||||||||||
| _memory.Span.CopyTo(copy); | ||||||||||||||||||||||||||||||||||||
| return copy; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
jozkee marked this conversation as resolved.
jozkee marked this conversation as resolved.
jozkee marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override void WriteTo(Stream stream) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| ArgumentNullException.ThrowIfNull(stream); | ||||||||||||||||||||||||||||||||||||
| EnsureNotClosed(); | ||||||||||||||||||||||||||||||||||||
| public override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| stream.Write(_memory.Span); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override void WriteByte(byte value) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) => throw new NotSupportedException(SR.NotSupported_UnwritableStream); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// <inheritdoc/> | ||||||||||||||||||||||||||||||||||||
| protected override void Dispose(bool disposing) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| _isOpen = false; | ||||||||||||||||||||||||||||||||||||
| _memory = default; | ||||||||||||||||||||||||||||||||||||
| _lastReadTask = default; | ||||||||||||||||||||||||||||||||||||
| base.Dispose(disposing); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.