diff --git a/src/libraries/Common/src/System/IO/StreamHelpers.CopyValidation.cs b/src/libraries/Common/src/System/IO/StreamHelpers.CopyValidation.cs index 7dca7eba48d78d..45bbd816df0185 100644 --- a/src/libraries/Common/src/System/IO/StreamHelpers.CopyValidation.cs +++ b/src/libraries/Common/src/System/IO/StreamHelpers.CopyValidation.cs @@ -42,25 +42,5 @@ public static void ValidateCopyToArgs(Stream source, Stream destination, int buf throw new NotSupportedException(SR.NotSupported_UnwritableStream); } } - - public static void ValidateCopyToArgs(Stream source, Delegate callback, int bufferSize) - { - if (callback == null) - { - throw new ArgumentNullException(nameof(callback)); - } - - if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, SR.ArgumentOutOfRange_NeedPosNum); - } - - if (!source.CanRead) - { - throw source.CanWrite ? (Exception) - new NotSupportedException(SR.NotSupported_UnreadableStream) : - new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed); - } - } } } diff --git a/src/libraries/System.IO/tests/Stream/Stream.CopyToSpanTests.cs b/src/libraries/System.IO/tests/Stream/Stream.CopyToSpanTests.cs deleted file mode 100644 index a9b57ad92eb64f..00000000000000 --- a/src/libraries/System.IO/tests/Stream/Stream.CopyToSpanTests.cs +++ /dev/null @@ -1,535 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Tests -{ - public partial class StreamCopyToSpanTests - { - [Fact] - public void CopyTo_InvalidArgsThrows() - { - using Stream s = new MemoryStream(); - - AssertExtensions.Throws("callback", () => s.CopyTo(null, null, 0)); - AssertExtensions.Throws("bufferSize", () => s.CopyTo((_, __) => { }, null, 0)); - - AssertExtensions.Throws("callback", () => s.CopyToAsync(null, null, 0, default)); - AssertExtensions.Throws("bufferSize", () => s.CopyToAsync((_, __, ___) => default, null, 0, default)); - } - - [Fact] - public void CopyToAsync_PrecanceledToken_Cancels() - { - using var src = new MemoryStream(); - Assert.Equal(TaskStatus.Canceled, src.CopyToAsync((_, __, ___) => default, null, 4096, new CancellationToken(true)).Status); - } - - [Theory] - [MemberData(nameof(CopyTo_TestData))] - public async Task CopyToAsync_CancellationToken_Propagated(MemoryStream input) - { - using var src = input; - src.WriteByte(0); - src.Position = 0; - - CancellationToken cancellationToken = new CancellationTokenSource().Token; - CancellationToken expectedToken = (input is CustomMemoryStream cms && cms.Sync) ? default(CancellationToken) : cancellationToken; - await src.CopyToAsync( - (_, __, token) => new ValueTask(Task.Run(() => Assert.Equal(expectedToken, token))), - null, - 4096, - cancellationToken - ); - } - - [Theory] - [MemberData(nameof(CopyTo_TestData))] - public async Task CopyToAsync_State_Propagated(MemoryStream input) - { - using var src = input; - src.WriteByte(0); - src.Position = 0; - - const int expected = 42; - await src.CopyToAsync( - (_, state, __) => new ValueTask(Task.Run(() => Assert.Equal(expected, state))), - expected, - 4096, - default - ); - } - - [Theory] - [InlineData(0)] - [InlineData(42)] - [InlineData(100000)] - public void CopyToAsync_StreamToken_ExpectedBufferSizePropagated(int length) - { - using var src = new CustomMemoryStream_BufferSize(); - src.Write(new byte[length], 0, length); - src.Position = 0; - - Assert.Equal(length, ((Task)src.CopyToAsync((_, __, ___) => default, null, length, default(CancellationToken))).Result); - } - - private sealed class CustomMemoryStream_BufferSize : MemoryStream - { - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => - Task.FromResult(bufferSize); - } - - [Theory] - [MemberData(nameof(CopyTo_TestData))] - public void CopyTo_AllDataCopied(MemoryStream input) - { - using var src = input; - src.Write(Enumerable.Range(0, 10000).Select(i => (byte)i).ToArray(), 0, 256); - src.Position = 0; - - using var dst = new MemoryStream(); - src.CopyTo((span, _) => dst.Write(span), null, 4096); - - Assert.Equal(src.ToArray(), dst.ToArray()); - } - - [Theory] - [MemberData(nameof(CopyTo_TestData))] - public async Task CopyToAsync_AllDataCopied(MemoryStream input) - { - using var src = input; - src.Write(Enumerable.Range(0, 10000).Select(i => (byte)i).ToArray(), 0, 256); - src.Position = 0; - - using var dst = new MemoryStream(); - await src.CopyToAsync((memory, _, ___) => dst.WriteAsync(memory), null, 4096, default); - - Assert.Equal(src.ToArray(), dst.ToArray()); - } - - private sealed class CustomMemoryStream : MemoryStream - { - private readonly bool _spanCopy; - private readonly bool _sync; - - public bool Sync => _sync; - - public CustomMemoryStream(bool spanCopy, bool sync) - : base() - { - _spanCopy = spanCopy; - _sync = sync; - } - - public override void CopyTo(Stream destination, int bufferSize) - { - if (_sync) - { - CopyToInternal(destination, bufferSize); - } - else - { - CopyToAsyncInternal(destination, bufferSize, CancellationToken.None).GetAwaiter().GetResult(); - } - } - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - if (!_sync) - { - return CopyToAsyncInternal(destination, bufferSize, cancellationToken); - } - else - { - try - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - CopyToInternal(destination, bufferSize); - return Task.CompletedTask; - } - catch (Exception e) - { - return Task.FromException(e); - } - } - } - - private void CopyToInternal(Stream destination, int bufferSize) - { - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - int read; - while ((read = Read(buffer, 0, buffer.Length)) != 0) - { - if (_spanCopy) - destination.Write(new ReadOnlySpan(buffer, 0, read)); - else - destination.Write(buffer, 0, read); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - private async Task CopyToAsyncInternal(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool.Shared.Rent(bufferSize); - try - { - while (true) - { - int bytesRead = await ReadAsync(new Memory(buffer), cancellationToken).ConfigureAwait(false); - if (bytesRead == 0) break; - if (_spanCopy) - await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); - else - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - } - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - } - - public static IEnumerable CopyTo_TestData() - { - foreach (var sync in new[] { false, true }) - foreach (var spanCopy in new[] { false, true }) - yield return new object[] { new CustomMemoryStream(spanCopy, sync) }; - - yield return new object[] { new MemoryStream() }; - } - - [Fact] - public void IfCanSeekIsFalseLengthAndPositionShouldNotBeCalled() - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => false, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - trackingStream.CopyTo((_, __) => { }, null, 1); - - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.CanSeek)), 0, 1); - Assert.Equal(0, trackingStream.TimesCalled(nameof(trackingStream.Length))); - Assert.Equal(0, trackingStream.TimesCalled(nameof(trackingStream.Position))); - // We can't override CopyTo since it's not virtual, so checking TimesCalled - // for CopyTo will result in 0. Instead, we check that Read was called, - // and validate the parameters passed there. - Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read))); - - byte[] outerBuffer = trackingStream.ReadBuffer; - int outerOffset = trackingStream.ReadOffset; - int outerCount = trackingStream.ReadCount; - - Assert.NotNull(outerBuffer); - Assert.InRange(outerOffset, 0, outerBuffer.Length - outerCount); - Assert.InRange(outerCount, 1, int.MaxValue); // the buffer can't be size 0 - } - - [Fact] - public async Task AsyncIfCanSeekIsFalseLengthAndPositionShouldNotBeCalled() - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => false, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - await trackingStream.CopyToAsync((_, __, ___) => default, null, 1, default(CancellationToken)); - - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.CanSeek)), 0, 1); - Assert.Equal(0, trackingStream.TimesCalled(nameof(trackingStream.Length))); - Assert.Equal(0, trackingStream.TimesCalled(nameof(trackingStream.Position))); - Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.CopyToAsync))); - - Assert.InRange(trackingStream.CopyToAsyncBufferSize, 1, int.MaxValue); - Assert.Equal(default(CancellationToken), trackingStream.CopyToAsyncCancellationToken); - } - - [Fact] - public void IfCanSeekIsTrueLengthAndPositionShouldOnlyBeCalledOnce() - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - readFunc: (buffer, offset, count) => 0, - lengthFunc: () => 0L, - positionGetFunc: () => 0L); - var trackingStream = new CallTrackingStream(baseStream); - - trackingStream.CopyTo((_, __) => { }, null, 1); - - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.Length)), 0, 1); - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.Position)), 0, 1); - } - - [Fact] - public async Task AsyncIfCanSeekIsTrueLengthAndPositionShouldOnlyBeCalledOnce() - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - readFunc: (buffer, offset, count) => 0, - lengthFunc: () => 0L, - positionGetFunc: () => 0L); - var trackingStream = new CallTrackingStream(baseStream); - - await trackingStream.CopyToAsync((_, __, ___) => default, null, 1, default(CancellationToken)); - - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.Length)), 0, 1); - Assert.InRange(trackingStream.TimesCalled(nameof(trackingStream.Position)), 0, 1); - } - - [Theory] - [MemberData(nameof(LengthIsLessThanOrEqualToPosition))] - public void IfLengthIsLessThanOrEqualToPositionCopyToShouldStillBeCalledWithAPositiveBufferSize(long length, long position) - { - // Streams with their Lengths <= their Positions, e.g. - // new MemoryStream { Position = 3 }.SetLength(1) - // should still be called CopyTo{Async} on with a - // bufferSize of at least 1. - - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - trackingStream.CopyTo((_, __) => { }, null, 1); - - // CopyTo is not virtual, so we can't override it in - // CallTrackingStream and record the arguments directly. - // Instead, validate the arguments passed to Read. - - Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read))); - - byte[] outerBuffer = trackingStream.ReadBuffer; - int outerOffset = trackingStream.ReadOffset; - int outerCount = trackingStream.ReadCount; - - Assert.NotNull(outerBuffer); - Assert.InRange(outerOffset, 0, outerBuffer.Length - outerCount); - Assert.InRange(outerCount, 1, int.MaxValue); - } - - [Theory] - [MemberData(nameof(LengthIsLessThanOrEqualToPosition))] - public async Task AsyncIfLengthIsLessThanOrEqualToPositionCopyToShouldStillBeCalledWithAPositiveBufferSize(long length, long position) - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - await trackingStream.CopyToAsync((_, __, ___) => default, null, 1, default(CancellationToken)); - - Assert.InRange(trackingStream.CopyToAsyncBufferSize, 1, int.MaxValue); - Assert.Equal(default(CancellationToken), trackingStream.CopyToAsyncCancellationToken); - } - - [Theory] - [MemberData(nameof(LengthMinusPositionPositiveOverflows))] - public void IfLengthMinusPositionPositiveOverflowsBufferSizeShouldStillBePositive(long length, long position) - { - // The new implementation of Stream.CopyTo calculates the bytes left - // in the Stream by calling Length - Position. This can overflow to a - // negative number, so this tests that if that happens we don't send - // in a negative bufferSize. - - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - trackingStream.CopyTo((_, __) => { }, null, 1); - - // CopyTo is not virtual, so we can't override it in - // CallTrackingStream and record the arguments directly. - // Instead, validate the arguments passed to Read. - - Assert.Equal(1, trackingStream.TimesCalled(nameof(trackingStream.Read))); - - byte[] outerBuffer = trackingStream.ReadBuffer; - int outerOffset = trackingStream.ReadOffset; - int outerCount = trackingStream.ReadCount; - - Assert.NotNull(outerBuffer); - Assert.InRange(outerOffset, 0, outerBuffer.Length - outerCount); - Assert.InRange(outerCount, 1, int.MaxValue); - } - - [Theory] - [MemberData(nameof(LengthMinusPositionPositiveOverflows))] - public async Task AsyncIfLengthMinusPositionPositiveOverflowsBufferSizeShouldStillBePositive(long length, long position) - { - var baseStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => 0); - var trackingStream = new CallTrackingStream(baseStream); - - await trackingStream.CopyToAsync((_, __, ___) => default, null, 1, default(CancellationToken)); - - // Note: We can't check how many times ReadAsync was called - // here, since trackingStream overrides CopyToAsync and forwards - // to the inner (non-tracking) stream for the implementation - - Assert.InRange(trackingStream.CopyToAsyncBufferSize, 1, int.MaxValue); - Assert.Equal(default(CancellationToken), trackingStream.CopyToAsyncCancellationToken); - } - - [Theory] - [MemberData(nameof(LengthIsGreaterThanPositionAndDoesNotOverflow))] - public void IfLengthIsGreaterThanPositionAndDoesNotOverflowEverythingShouldGoNormally(long length, long position) - { - const int ReadLimit = 7; - - // Lambda state - byte[] outerBuffer = null; - int? outerOffset = null; - int? outerCount = null; - int readsLeft = ReadLimit; - - var srcBase = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => - { - Assert.NotNull(buffer); - Assert.InRange(offset, 0, buffer.Length - count); - Assert.InRange(count, 1, int.MaxValue); - - // CopyTo should always pass in the same buffer/offset/count - - if (outerBuffer != null) Assert.Same(outerBuffer, buffer); - else outerBuffer = buffer; - - if (outerOffset != null) Assert.Equal(outerOffset, offset); - else outerOffset = offset; - - if (outerCount != null) Assert.Equal(outerCount, count); - else outerCount = count; - - return --readsLeft; // CopyTo will call Read on this ReadLimit times before stopping - }); - - var src = new CallTrackingStream(srcBase); - - int timesCalled = 0; - src.CopyTo((_, __) => { timesCalled++; }, null, 1); - - Assert.Equal(ReadLimit, src.TimesCalled(nameof(src.Read))); - Assert.Equal(ReadLimit - 1, timesCalled); - } - - [Theory] - [MemberData(nameof(LengthIsGreaterThanPositionAndDoesNotOverflow))] - public async Task AsyncIfLengthIsGreaterThanPositionAndDoesNotOverflowEverythingShouldGoNormally(long length, long position) - { - const int ReadLimit = 7; - - // Lambda state - byte[] outerBuffer = null; - int? outerOffset = null; - int? outerCount = null; - int readsLeft = ReadLimit; - - var srcBase = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => true, - lengthFunc: () => length, - positionGetFunc: () => position, - readFunc: (buffer, offset, count) => - { - Assert.NotNull(buffer); - Assert.InRange(offset, 0, buffer.Length - count); - Assert.InRange(count, 1, int.MaxValue); - - // CopyTo should always pass in the same buffer/offset/count - - if (outerBuffer != null) Assert.Same(outerBuffer, buffer); - else outerBuffer = buffer; - - if (outerOffset != null) Assert.Equal(outerOffset, offset); - else outerOffset = offset; - - if (outerCount != null) Assert.Equal(outerCount, count); - else outerCount = count; - - return --readsLeft; // CopyTo will call Read on this ReadLimit times before stopping - }); - - var src = new CallTrackingStream(srcBase); - - int timesCalled = 0; - await src.CopyToAsync((_, __, ___) => { timesCalled++; return default; }, null, 1, default(CancellationToken)); - - // Since we override CopyToAsync in CallTrackingStream, - // src.Read will actually not get called ReadLimit - // times, src.Inner.Read will. So, we just assert that - // CopyToAsync was called once for src. - - Assert.Equal(1, src.TimesCalled(nameof(src.CopyToAsync))); - Assert.Equal(ReadLimit - 1, timesCalled); // dest.WriteAsync will still get called repeatedly - } - - // Member data - - public static IEnumerable LengthIsLessThanOrEqualToPosition() - { - yield return new object[] { 5L, 5L }; // same number - yield return new object[] { 3L, 5L }; // length is less than position - yield return new object[] { -1L, -1L }; // negative numbers - yield return new object[] { 0L, 0L }; // both zero - yield return new object[] { -500L, 0L }; // negative number and zero - yield return new object[] { 0L, 500L }; // zero and positive number - yield return new object[] { -500L, 500L }; // negative and positive number - yield return new object[] { long.MinValue, long.MaxValue }; // length - position <= 0 will fail (overflow), but length <= position won't - } - - public static IEnumerable LengthMinusPositionPositiveOverflows() - { - yield return new object[] { long.MaxValue, long.MinValue }; // length - position will be -1 - yield return new object[] { 1L, -long.MaxValue }; - } - - public static IEnumerable LengthIsGreaterThanPositionAndDoesNotOverflow() - { - yield return new object[] { 5L, 3L }; - yield return new object[] { -3L, -6L }; - yield return new object[] { 0L, -3L }; - yield return new object[] { long.MaxValue, 0 }; // should not overflow or OOM - yield return new object[] { 85000, 123 }; // at least in the current implementation, we max out the bufferSize at 81920 - } - } -} diff --git a/src/libraries/System.IO/tests/System.IO.Tests.csproj b/src/libraries/System.IO/tests/System.IO.Tests.csproj index 44cd574154b951..301c0274c93bfa 100644 --- a/src/libraries/System.IO/tests/System.IO.Tests.csproj +++ b/src/libraries/System.IO/tests/System.IO.Tests.csproj @@ -39,7 +39,6 @@ - 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 3d8bedce68b3e7..8ced94adb6409d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/MemoryStream.cs @@ -544,51 +544,6 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio } } - public override void CopyTo(ReadOnlySpanAction callback, object? state, int bufferSize) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() which a subclass might have overridden. - // To be safe we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into Read) when we are not sure. - if (GetType() != typeof(MemoryStream)) - { - base.CopyTo(callback, state, bufferSize); - return; - } - - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // Retrieve a span until the end of the MemoryStream. - ReadOnlySpan span = new ReadOnlySpan(_buffer, _position, _length - _position); - _position = _length; - - // Invoke the callback, using our internal span and avoiding any - // intermediary allocations. - callback(span, state); - } - - public override Task CopyToAsync(Func, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to ReadAsync() which a subclass might have overridden. - // To be safe we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into ReadAsync) when we are not sure. - if (GetType() != typeof(MemoryStream)) - return base.CopyToAsync(callback, state, bufferSize, cancellationToken); - - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // If canceled - return fast: - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - // Avoid copying data from this buffer into a temp buffer - ReadOnlyMemory memory = new ReadOnlyMemory(_buffer, _position, _length - _position); - _position = _length; - - return callback(memory, state, cancellationToken).AsTask(); - } - public override long Seek(long offset, SeekOrigin loc) { EnsureNotClosed(); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs index 32a287082a9151..3e815bdeab55be 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs @@ -188,99 +188,6 @@ private int GetCopyBufferSize() return bufferSize; } - public virtual void CopyTo(ReadOnlySpanAction callback, object? state, int bufferSize) - { - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - CopyTo(new WriteCallbackStream(callback, state), bufferSize); - } - - public virtual Task CopyToAsync(Func, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - return CopyToAsync(new WriteCallbackStream(callback, state), bufferSize, cancellationToken); - } - - private sealed class WriteCallbackStream : Stream - { - private readonly ReadOnlySpanAction? _action; - private readonly Func, object?, CancellationToken, ValueTask>? _func; - private readonly object? _state; - - public WriteCallbackStream(ReadOnlySpanAction action, object? state) - { - _action = action; - _state = state; - } - - public WriteCallbackStream(Func, object?, CancellationToken, ValueTask> func, object? state) - { - _func = func; - _state = state; - } - - public override void Write(byte[] buffer, int offset, int count) - { - Write(new ReadOnlySpan(buffer, offset, count)); - } - - public override void Write(ReadOnlySpan span) - { - if (_action != null) - { - _action(span, _state); - return; - } - - // In case a poorly implemented CopyToAsync(Stream, ...) method decides to call - // the destination stream's Write rather than WriteAsync, we make it work, but this - // does not need to be efficient. - Debug.Assert(_func != null); - _func(span.ToArray(), _state, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - - } - - public override Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) - { - return WriteAsync(new ReadOnlyMemory(buffer, offset, length), cancellationToken).AsTask(); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) - { - if (_func != null) - { - return _func(buffer, _state, cancellationToken); - } - - // In case a poorly implemented CopyTo(Stream, ...) method decides to call - // the destination stream's WriteAsync rather than Write, we make it work, - // but this does not need to be efficient. - Debug.Assert(_action != null); - try - { - cancellationToken.ThrowIfCancellationRequested(); - _action(buffer.Span, _state); - return default; - } - catch (Exception e) - { - return ValueTask.FromException(e); - } - } - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override void Flush() { } - public override Task FlushAsync(CancellationToken token) => Task.CompletedTask; - public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - } - // Stream used to require that all cleanup logic went into Close(), // which was thought up before we invented IDisposable. However, we // need to follow the IDisposable pattern so that users can write @@ -978,22 +885,6 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio Task.CompletedTask; } - public override void CopyTo(ReadOnlySpanAction callback, object? state, int bufferSize) - { - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // After we validate arguments this is a nop. - } - - public override Task CopyToAsync(Func, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; - } - protected override void Dispose(bool disposing) { // Do nothing - we don't want NullStream singleton (static) to be closable diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/UnmanagedMemoryStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/UnmanagedMemoryStream.cs index 135a6d665807c9..3ec892c1c98d01 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/UnmanagedMemoryStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/UnmanagedMemoryStream.cs @@ -206,75 +206,6 @@ protected unsafe void Initialize(byte* pointer, long length, long capacity, File /// public override bool CanWrite => _isOpen && (_access & FileAccess.Write) != 0; - /// - /// Calls the given callback with a span of the memory stream data - /// - /// the callback to be called - /// A user-defined state, passed to the callback - /// the maximum size of the memory span - public override void CopyTo(ReadOnlySpanAction callback, object? state, int bufferSize) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() which a subclass might have overridden. - // To be safe we will only use this implementation in cases where we know it is safe to do so, - // and delegate to our base class (which will call into Read) when we are not sure. - if (GetType() != typeof(UnmanagedMemoryStream)) - { - base.CopyTo(callback, state, bufferSize); - return; - } - - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - EnsureNotClosed(); - EnsureReadable(); - - // Use a local variable to avoid a race where another thread - // changes our position after we decide we can read some bytes. - long pos = Interlocked.Read(ref _position); - long len = Interlocked.Read(ref _length); - long n = len - pos; - if (n <= 0) - { - return; - } - - int nInt = (int)n; // Safe because n <= count, which is an Int32 - if (nInt < 0) - { - return; // _position could be beyond EOF - } - - unsafe - { - if (_buffer != null) - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - ReadOnlySpan span = new ReadOnlySpan(pointer + pos + _offset, nInt); - Interlocked.Exchange(ref _position, pos + n); - callback(span, state); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - else - { - ReadOnlySpan span = new ReadOnlySpan(_mem + pos, nInt); - Interlocked.Exchange(ref _position, pos + n); - callback(span, state); - } - } - } - /// /// Closes the stream. The stream's memory needs to be dealt with separately. /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index aee33c8d4a0305..1ea326cd2513d2 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -6984,9 +6984,7 @@ public MemoryStream(int capacity) { } public override long Position { get { throw null; } set { } } public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } - public override void CopyTo(System.Buffers.ReadOnlySpanAction callback, object? state, int bufferSize) { } public override void CopyTo(System.IO.Stream destination, int bufferSize) { } - public override System.Threading.Tasks.Task CopyToAsync(System.Func, object?, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask> callback, object? state, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } 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 int EndRead(System.IAsyncResult asyncResult) { throw null; } @@ -7093,14 +7091,12 @@ protected Stream() { } public virtual System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } public virtual System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback? callback, object? state) { throw null; } public virtual void Close() { } - public virtual void CopyTo(System.Buffers.ReadOnlySpanAction callback, object? state, int bufferSize) { } public void CopyTo(System.IO.Stream destination) { } public virtual void CopyTo(System.IO.Stream destination, int bufferSize) { } public System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination) { throw null; } public System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize) { throw null; } public virtual System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } public System.Threading.Tasks.Task CopyToAsync(System.IO.Stream destination, System.Threading.CancellationToken cancellationToken) { throw null; } - public virtual System.Threading.Tasks.Task CopyToAsync(System.Func, object?, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask> callback, object? state, int bufferSize, System.Threading.CancellationToken cancellationToken) { throw null; } [System.ObsoleteAttribute("CreateWaitHandle will be removed eventually. Please use \"new ManualResetEvent(false)\" instead.")] protected virtual System.Threading.WaitHandle CreateWaitHandle() { throw null; } public void Dispose() { } @@ -7371,7 +7367,6 @@ public UnmanagedMemoryStream(System.Runtime.InteropServices.SafeBuffer buffer, l public override long Position { get { throw null; } set { } } [System.CLSCompliantAttribute(false)] public unsafe byte* PositionPointer { get { throw null; } set { } } - public override void CopyTo(System.Buffers.ReadOnlySpanAction callback, object? state, int bufferSize) { } protected override void Dispose(bool disposing) { } public override void Flush() { } public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { throw null; }