Skip to content
Merged
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
5 changes: 5 additions & 0 deletions src/libraries/Common/src/System/IO/ReadOnlyMemoryStream.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// On net11.0+, the public ReadOnlyMemoryStream in System.Runtime (CoreLib) supersedes this internal copy.
#if !NET11_0_OR_GREATER

using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -213,3 +216,5 @@ private static void ValidateBufferArguments(byte[] buffer, int offset, int count
#endif
}
}

#endif // !NET11_0_OR_GREATER
4 changes: 2 additions & 2 deletions src/libraries/System.Memory.Data/tests/BinaryDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ public void ValidatesSeekArguments()
Assert.Throws<IOException>(() => stream.Seek(-1, SeekOrigin.Begin));

Assert.Throws<ArgumentOutOfRangeException>(() => stream.Seek((long)int.MaxValue + 1, SeekOrigin.Begin));
Assert.Throws<ArgumentOutOfRangeException>(() => stream.Seek(0, (SeekOrigin)3));
Assert.ThrowsAny<ArgumentException>(() => stream.Seek(0, (SeekOrigin)3));
}


Expand Down Expand Up @@ -715,7 +715,7 @@ public async Task CloseStreamValidation()
byte[] buffer = "some data"u8.ToArray();
Stream stream = new BinaryData(buffer).ToStream();
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => stream.Position = -1);
Assert.Throws<ObjectDisposedException>(() => stream.Position = 0);
Assert.Throws<ObjectDisposedException>(() => stream.Position);
Assert.Throws<ObjectDisposedException>(() => stream.Seek(0, SeekOrigin.Begin));
Assert.Throws<ObjectDisposedException>(() => stream.Read(buffer, 0, buffer.Length));
Expand Down
28 changes: 28 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,34 @@ public void Rewind(long count) { }
public bool TryReadExact(int count, out System.Buffers.ReadOnlySequence<T> sequence) { throw null; }
}
}
namespace System.Buffers
{
public sealed partial class ReadOnlySequenceStream : System.IO.Stream
{
public ReadOnlySequenceStream(System.Buffers.ReadOnlySequence<byte> source) { }
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 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 void Flush() { }
public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
public override int Read(byte[] buffer, int offset, int count) { throw null; }
public override int Read(System.Span<byte> buffer) { throw null; }
public override System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override int ReadByte() { throw null; }
public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; }
public override void SetLength(long value) { }
public override void Write(byte[] buffer, int offset, int count) { }
public override void Write(System.ReadOnlySpan<byte> 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<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
}
namespace System.Runtime.InteropServices
{
public static partial class SequenceMarshal
Expand Down
9 changes: 9 additions & 0 deletions src/libraries/System.Memory/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,13 @@
<data name="BufferMaximumSizeExceeded" xml:space="preserve">
<value>Cannot allocate a buffer of size {0}.</value>
</data>
<data name="NotSupported_UnwritableStream" xml:space="preserve">
<value>Stream does not support writing.</value>
</data>
<data name="IO_SeekBeforeBegin" xml:space="preserve">
<value>An attempt was made to move the position before the beginning of the stream.</value>
</data>
<data name="Argument_InvalidSeekOrigin" xml:space="preserve">
<value>Invalid seek origin.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/libraries/System.Memory/src/System.Memory.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Compile Include="System\Buffers\ReadOnlySequence.cs" />
<Compile Include="System\Buffers\ReadOnlySequenceDebugView.cs" />
<Compile Include="System\Buffers\ReadOnlySequenceSegment.cs" />
<Compile Include="System\Buffers\ReadOnlySequenceStream.cs" />
<Compile Include="System\Buffers\ReadOnlySequence.Helpers.cs" />
<Compile Include="System\Buffers\SequenceReader.cs" />
<Compile Include="System\Buffers\SequenceReader.Search.cs" />
Expand All @@ -40,6 +41,8 @@
<!-- Common or Common-branched source files -->
<Compile Include="$(CommonPath)System\Buffers\ArrayBufferWriter.cs"
Link="Common\System\Buffers\ArrayBufferWriter.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\CachedCompletedInt32Task.cs"
Link="Common\System\Threading\Tasks\CachedCompletedInt32Task.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace System.Buffers
{
/// <summary>
/// Provides a seekable, read-only <see cref="Stream"/> over a <see cref="ReadOnlySequence{Byte}"/>.
/// </summary>
/// <remarks>
/// <para>The underlying sequence is not copied; reads are served directly from its segments.</para>
/// <para>The stream cannot be written to. <see cref="CanWrite"/> always returns <see langword="false"/>.</para>
/// </remarks>
public sealed class ReadOnlySequenceStream : Stream
{
private ReadOnlySequence<byte> _sequence;
private SequencePosition _position;
private long _absolutePosition;
private bool _isDisposed;
private CachedCompletedInt32Task _lastReadTask;

/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlySequenceStream"/> class over the specified <see cref="ReadOnlySequence{Byte}"/>.
/// </summary>
/// <param name="source">The <see cref="ReadOnlySequence{Byte}"/> to wrap.</param>
public ReadOnlySequenceStream(ReadOnlySequence<byte> source)
{
_sequence = source;
_position = source.Start;
_absolutePosition = 0;
_isDisposed = false;
}

/// <inheritdoc />
public override bool CanRead => !_isDisposed;

/// <inheritdoc />
public override bool CanSeek => !_isDisposed;

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

private void EnsureNotDisposed() => ObjectDisposedException.ThrowIf(_isDisposed, this);

/// <inheritdoc />
public override long Length
{
get
{
EnsureNotDisposed();
return _sequence.Length;
}
}

/// <inheritdoc />
public override long Position
{
get
{
EnsureNotDisposed();
return _absolutePosition;
}
set
{
EnsureNotDisposed();
ArgumentOutOfRangeException.ThrowIfNegative(value);

if (value >= _sequence.Length)
{
_position = _sequence.End;
}
else if (value >= _absolutePosition)
{
_position = _sequence.GetPosition(value - _absolutePosition, _position);
}
else
{
_position = _sequence.GetPosition(value, _sequence.Start);
}

_absolutePosition = value;
}
}

/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
return Read(buffer.AsSpan(offset, count));
}

/// <inheritdoc />
public override int Read(Span<byte> buffer)
{
EnsureNotDisposed();

if (_absolutePosition >= _sequence.Length)
{
return 0;
}

ReadOnlySequence<byte> remaining = _sequence.Slice(_position);
int n = (int)Math.Min(remaining.Length, buffer.Length);
if (n <= 0)
{
return 0;
}

remaining.Slice(0, n).CopyTo(buffer);
_position = _sequence.GetPosition(n, _position);
_absolutePosition += n;
return n;
}

/// <inheritdoc />
public override int ReadByte()
{
EnsureNotDisposed();

byte b = 0;
return Read(new Span<byte>(ref b)) > 0 ? b : -1;
}

/// <inheritdoc/>
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArguments(buffer, offset, count);
EnsureNotDisposed();

if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}

int n = Read(buffer, offset, count);
return _lastReadTask.GetTask(n);
}

/// <inheritdoc/>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
EnsureNotDisposed();

if (cancellationToken.IsCancellationRequested)
{
return ValueTask.FromCanceled<int>(cancellationToken);
}

int n = Read(buffer.Span);
return new ValueTask<int>(n);
}

/// <inheritdoc />
public override void CopyTo(Stream destination, int bufferSize)
{
ValidateCopyToArguments(destination, bufferSize);
EnsureNotDisposed();

if (_absolutePosition >= _sequence.Length)
{
return;
}

ReadOnlySequence<byte> remaining = _sequence.Slice(_position);
foreach (ReadOnlyMemory<byte> segment in remaining)
{
destination.Write(segment.Span);
}

_position = _sequence.End;
_absolutePosition = _sequence.Length;
}

/// <inheritdoc />
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
ValidateCopyToArguments(destination, bufferSize);
EnsureNotDisposed();

if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

if (_absolutePosition >= _sequence.Length)
{
return Task.CompletedTask;
}

return CopyToAsyncCore(destination, cancellationToken);
}

private async Task CopyToAsyncCore(Stream destination, CancellationToken cancellationToken)
{
ReadOnlySequence<byte> remaining = _sequence.Slice(_position);
foreach (ReadOnlyMemory<byte> segment in remaining)
{
await destination.WriteAsync(segment, cancellationToken).ConfigureAwait(false);
}

_position = _sequence.End;
_absolutePosition = _sequence.Length;
}

/// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(SR.NotSupported_UnwritableStream);

/// <inheritdoc/>
public override void Write(ReadOnlySpan<byte> buffer) => 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/>
public override long Seek(long offset, SeekOrigin origin)
{
EnsureNotDisposed();

long basePosition = origin switch
{
SeekOrigin.Begin => 0L,
SeekOrigin.Current => _absolutePosition,
SeekOrigin.End => _sequence.Length,
_ => throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin))
};

ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, long.MaxValue - basePosition);

long absolutePosition = basePosition + offset;

if (absolutePosition < 0)
{
throw new IOException(SR.IO_SeekBeforeBegin);
}

if (absolutePosition >= _sequence.Length)
{
_position = _sequence.End;
}
else if (absolutePosition >= _absolutePosition)
{
_position = _sequence.GetPosition(absolutePosition - _absolutePosition, _position);
}
else
{
_position = _sequence.GetPosition(absolutePosition, _sequence.Start);
}

_absolutePosition = absolutePosition;
return absolutePosition;
}

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

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

/// <inheritdoc />
public override void SetLength(long value) => throw new NotSupportedException(SR.NotSupported_UnwritableStream);

/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_isDisposed = true;
_sequence = default;
base.Dispose(disposing);
}
}
}
Loading
Loading