diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs
index fbcef472cb5..cb4ec0f2829 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs
@@ -2,11 +2,12 @@
// 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;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
-using Microsoft.Toolkit.HighPerformance.Streams;
+using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@@ -18,17 +19,18 @@ public static class IMemoryOwnerExtensions
///
/// Returns a wrapping the contents of the given of instance.
///
- /// The input of instance.
- /// A wrapping the data within .
+ /// The input of instance.
+ /// A wrapping the data within .
///
/// The caller does not need to track the lifetime of the input of
/// instance, as the returned will take care of disposing that buffer when it is closed.
///
+ /// Thrown when has an invalid data store.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Stream AsStream(this IMemoryOwner memory)
+ public static Stream AsStream(this IMemoryOwner memoryOwner)
{
- return new IMemoryOwnerStream(memory);
+ return MemoryStream.Create(memoryOwner);
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
index 8c1e053a186..794339fbbed 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
@@ -26,11 +26,12 @@ public static class MemoryExtensions
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned is in use, to avoid unexpected issues.
///
+ /// Thrown when has an invalid data store.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this Memory memory)
{
- return new MemoryStream(memory);
+ return MemoryStream.Create(memory, false);
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
index 46101e3db89..eab50592776 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
@@ -5,7 +5,6 @@
using System;
using System.Diagnostics.Contracts;
using System.IO;
-using System.Runtime.CompilerServices;
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@@ -26,11 +25,11 @@ public static class ReadOnlyMemoryExtensions
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned is in use, to avoid unexpected issues.
///
+ /// Thrown when has an invalid data store.
[Pure]
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ReadOnlyMemory memory)
{
- return new MemoryStream(memory);
+ return MemoryStream.Create(memory, true);
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs
similarity index 50%
rename from Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs
rename to Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs
index c9a0a428e4b..2dfe66e215e 100644
--- a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs
+++ b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs
@@ -2,6 +2,7 @@
// 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;
using System.Buffers;
using System.IO;
@@ -10,21 +11,24 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
///
/// A implementation wrapping an of instance.
///
- internal sealed class IMemoryOwnerStream : MemoryStream
+ /// The type of source to use for the underlying data.
+ internal sealed class IMemoryOwnerStream : MemoryStream
+ where TSource : struct, ISpanOwner
{
///
- /// The of instance currently in use.
+ /// The instance currently in use.
///
- private readonly IMemoryOwner memory;
+ private readonly IDisposable disposable;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The input of instance to use.
- public IMemoryOwnerStream(IMemoryOwner memory)
- : base(memory.Memory)
+ /// The input instance to use.
+ /// The instance currently in use.
+ public IMemoryOwnerStream(TSource source, IDisposable disposable)
+ : base(source, false)
{
- this.memory = memory;
+ this.disposable = disposable;
}
///
@@ -32,7 +36,7 @@ protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- this.memory.Dispose();
+ this.disposable.Dispose();
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs
index 209c935e4a5..cdfc24112d5 100644
--- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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.
@@ -8,16 +8,41 @@
namespace Microsoft.Toolkit.HighPerformance.Streams
{
///
- /// A implementation wrapping a or instance.
+ /// A factory class to produce instances.
///
- internal partial class MemoryStream
+ internal static partial class MemoryStream
{
+ ///
+ /// Throws an when trying to write too many bytes to the target stream.
+ ///
+ public static void ThrowArgumentExceptionForEndOfStreamOnWrite()
+ {
+ throw new ArgumentException("The current stream can't contain the requested input data.");
+ }
+
+ ///
+ /// Throws a when trying to set the length of the stream.
+ ///
+ public static void ThrowNotSupportedExceptionForSetLength()
+ {
+ throw new NotSupportedException("Setting the length is not supported for this stream.");
+ }
+
+ ///
+ /// Throws an when using an invalid seek mode.
+ ///
+ /// Nothing, as this method throws unconditionally.
+ public static long ThrowArgumentExceptionForSeekOrigin()
+ {
+ throw new ArgumentException("The input seek mode is not valid.", "origin");
+ }
+
///
/// Throws an when setting the property.
///
private static void ThrowArgumentOutOfRangeExceptionForPosition()
{
- throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
+ throw new ArgumentOutOfRangeException(nameof(Stream.Position), "The value for the property was not in the valid range.");
}
///
@@ -60,37 +85,12 @@ private static void ThrowNotSupportedExceptionForCanWrite()
throw new NotSupportedException("The current stream doesn't support writing.");
}
- ///
- /// Throws an when trying to write too many bytes to the target stream.
- ///
- private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
- {
- throw new ArgumentException("The current stream can't contain the requested input data.");
- }
-
- ///
- /// Throws a when trying to set the length of the stream.
- ///
- private static void ThrowNotSupportedExceptionForSetLength()
- {
- throw new NotSupportedException("Setting the length is not supported for this stream.");
- }
-
- ///
- /// Throws an when using an invalid seek mode.
- ///
- /// Nothing, as this method throws unconditionally.
- private static long ThrowArgumentExceptionForSeekOrigin()
- {
- throw new ArgumentException("The input seek mode is not valid.", "origin");
- }
-
///
/// Throws an when using a disposed instance.
///
private static void ThrowObjectDisposedException()
{
- throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
+ throw new ObjectDisposedException("source", "The current stream has already been disposed");
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs
index 7f5cf07c29f..9cd0ff62936 100644
--- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs
+++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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.
@@ -8,9 +8,9 @@
namespace Microsoft.Toolkit.HighPerformance.Streams
{
///
- /// A implementation wrapping a or instance.
+ /// A factory class to produce instances.
///
- internal partial class MemoryStream
+ internal static partial class MemoryStream
{
///
/// Validates the argument.
@@ -18,7 +18,7 @@ internal partial class MemoryStream
/// The new value being set.
/// The maximum length of the target .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void ValidatePosition(long position, int length)
+ public static void ValidatePosition(long position, int length)
{
if ((ulong)position >= (ulong)length)
{
@@ -33,7 +33,7 @@ private static void ValidatePosition(long position, int length)
/// The offset within .
/// The number of elements to process within .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void ValidateBuffer(byte[]? buffer, int offset, int count)
+ public static void ValidateBuffer(byte[]? buffer, int offset, int count)
{
if (buffer is null)
{
@@ -57,24 +57,24 @@ private static void ValidateBuffer(byte[]? buffer, int offset, int count)
}
///
- /// Validates the property.
+ /// Validates the property.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ValidateCanWrite()
+ public static void ValidateCanWrite(bool canWrite)
{
- if (!CanWrite)
+ if (!canWrite)
{
ThrowNotSupportedExceptionForCanWrite();
}
}
///
- /// Validates that the current instance hasn't been disposed.
+ /// Validates that a given instance hasn't been disposed.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ValidateDisposed()
+ public static void ValidateDisposed(bool disposed)
{
- if (this.disposed)
+ if (disposed)
{
ThrowObjectDisposedException();
}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs
index e8682014721..cd2908bd881 100644
--- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs
+++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs
@@ -1,315 +1,95 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// 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;
+using System.Buffers;
+using System.Diagnostics.Contracts;
using System.IO;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
namespace Microsoft.Toolkit.HighPerformance.Streams
{
///
- /// A implementation wrapping a or instance.
+ /// A factory class to produce instances.
///
- ///
- /// This type is not marked as so that it can be inherited by
- /// , which adds the support for
- /// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT
- /// not being able to resolve the instruction, as this type is
- /// only exposed as a anyway, so the generated code would be the same.
- ///
- internal partial class MemoryStream : Stream
+ internal static partial class MemoryStream
{
///
- /// Indicates whether was actually a instance.
+ /// Creates a new from the input of instance.
///
- private readonly bool isReadOnly;
-
- ///
- /// The instance currently in use.
- ///
- private Memory memory;
-
- ///
- /// The current position within .
- ///
- private int position;
-
- ///
- /// Indicates whether or not the current instance has been disposed
- ///
- private bool disposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The input instance to use.
- public MemoryStream(Memory memory)
- {
- this.memory = memory;
- this.position = 0;
- this.isReadOnly = false;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The input instance to use.
- public MemoryStream(ReadOnlyMemory memory)
- {
- this.memory = MemoryMarshal.AsMemory(memory);
- this.position = 0;
- this.isReadOnly = true;
- }
-
- ///
- public sealed override bool CanRead
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => !this.disposed;
- }
-
- ///
- public sealed override bool CanSeek
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => !this.disposed;
- }
-
- ///
- public sealed override bool CanWrite
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => !this.isReadOnly && !this.disposed;
- }
-
- ///
- public sealed override long Length
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- ValidateDisposed();
-
- return this.memory.Length;
- }
- }
-
- ///
- public sealed override long Position
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- ValidateDisposed();
-
- return this.position;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- set
- {
- ValidateDisposed();
- ValidatePosition(value, this.memory.Length);
-
- this.position = unchecked((int)value);
- }
- }
-
- ///
- public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ /// The input instance.
+ /// Indicates whether or not can be written to.
+ /// A wrapping the underlying data for .
+ /// Thrown when has an invalid data store.
+ [Pure]
+ public static Stream Create(ReadOnlyMemory memory, bool isReadOnly)
{
- if (cancellationToken.IsCancellationRequested)
+ if (memory.IsEmpty)
{
- return Task.FromCanceled(cancellationToken);
+ // Return an empty stream if the memory was empty
+ return new MemoryStream(ArrayOwner.Empty, isReadOnly);
}
- try
+ if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment))
{
- CopyTo(destination, bufferSize);
+ var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count);
- return Task.CompletedTask;
- }
- catch (OperationCanceledException e)
- {
- return Task.FromCanceled(e.CancellationToken);
- }
- catch (Exception e)
- {
- return Task.FromException(e);
+ return new MemoryStream(arraySpanSource, isReadOnly);
}
- }
-
- ///
- public sealed override void Flush()
- {
- }
- ///
- public sealed override Task FlushAsync(CancellationToken cancellationToken)
- {
- if (cancellationToken.IsCancellationRequested)
+ if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length))
{
- return Task.FromCanceled(cancellationToken);
- }
-
- return Task.CompletedTask;
- }
+ MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length);
- ///
- public sealed override Task ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
+ return new MemoryStream(memoryManagerSpanSource, isReadOnly);
}
- try
- {
- int result = Read(buffer, offset, count);
-
- return Task.FromResult(result);
- }
- catch (OperationCanceledException e)
- {
- return Task.FromCanceled(e.CancellationToken);
- }
- catch (Exception e)
- {
- return Task.FromException(e);
- }
+ return ThrowNotSupportedExceptionForInvalidMemory();
}
- ///
- public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
+ ///
+ /// Creates a new from the input of instance.
+ ///
+ /// The input instance.
+ /// A wrapping the underlying data for .
+ /// Thrown when has an invalid data store.
+ [Pure]
+ public static Stream Create(IMemoryOwner memoryOwner)
{
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
+ Memory memory = memoryOwner.Memory;
- try
+ if (memory.IsEmpty)
{
- Write(buffer, offset, count);
-
- return Task.CompletedTask;
- }
- catch (OperationCanceledException e)
- {
- return Task.FromCanceled(e.CancellationToken);
+ // Return an empty stream if the memory was empty
+ return new IMemoryOwnerStream(ArrayOwner.Empty, memoryOwner);
}
- catch (Exception e)
- {
- return Task.FromException(e);
- }
- }
-
- ///
- public sealed override long Seek(long offset, SeekOrigin origin)
- {
- ValidateDisposed();
- long index = origin switch
+ if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment))
{
- SeekOrigin.Begin => offset,
- SeekOrigin.Current => this.position + offset,
- SeekOrigin.End => this.memory.Length + offset,
- _ => ThrowArgumentExceptionForSeekOrigin()
- };
-
- ValidatePosition(index, this.memory.Length);
-
- this.position = unchecked((int)index);
-
- return index;
- }
-
- ///
- public sealed override void SetLength(long value)
- {
- ThrowNotSupportedExceptionForSetLength();
- }
-
- ///
- public sealed override int Read(byte[]? buffer, int offset, int count)
- {
- ValidateDisposed();
- ValidateBuffer(buffer, offset, count);
-
- int
- bytesAvailable = this.memory.Length - this.position,
- bytesCopied = Math.Min(bytesAvailable, count);
-
- Span
- source = this.memory.Span.Slice(this.position, bytesCopied),
- destination = buffer.AsSpan(offset, bytesCopied);
-
- source.CopyTo(destination);
-
- this.position += bytesCopied;
+ var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count);
- return bytesCopied;
- }
-
- ///
- public sealed override int ReadByte()
- {
- ValidateDisposed();
-
- if (this.position == this.memory.Length)
- {
- return -1;
+ return new IMemoryOwnerStream(arraySpanSource, memoryOwner);
}
- return this.memory.Span[this.position++];
- }
-
- ///
- public sealed override void Write(byte[]? buffer, int offset, int count)
- {
- ValidateDisposed();
- ValidateCanWrite();
- ValidateBuffer(buffer, offset, count);
-
- Span
- source = buffer.AsSpan(offset, count),
- destination = this.memory.Span.Slice(this.position);
-
- if (!source.TryCopyTo(destination))
+ if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length))
{
- ThrowArgumentExceptionForEndOfStreamOnWrite();
- }
-
- this.position += source.Length;
- }
-
- ///
- public sealed override void WriteByte(byte value)
- {
- ValidateDisposed();
- ValidateCanWrite();
+ MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length);
- if (this.position == this.memory.Length)
- {
- ThrowArgumentExceptionForEndOfStreamOnWrite();
+ return new IMemoryOwnerStream(memoryManagerSpanSource, memoryOwner);
}
- this.memory.Span[this.position++] = value;
+ return ThrowNotSupportedExceptionForInvalidMemory();
}
- ///
- protected override void Dispose(bool disposing)
+ ///
+ /// Throws a when a given
+ /// or instance has an unsupported backing store.
+ ///
+ /// Nothing, this method always throws.
+ private static Stream ThrowNotSupportedExceptionForInvalidMemory()
{
- if (this.disposed)
- {
- return;
- }
-
- this.disposed = true;
- this.memory = default;
+ throw new ArgumentException("The input instance doesn't have a valid underlying data store.");
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs
similarity index 80%
rename from Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs
rename to Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs
index e576b94d8bf..e8c9c6fd76e 100644
--- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.NETStandard21.cs
+++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.Memory.cs
@@ -11,17 +11,15 @@
namespace Microsoft.Toolkit.HighPerformance.Streams
{
- ///
- /// A implementation wrapping a or instance.
- ///
- internal partial class MemoryStream
+ ///
+ internal partial class MemoryStream
{
///
public sealed override void CopyTo(Stream destination, int bufferSize)
{
- ValidateDisposed();
+ MemoryStream.ValidateDisposed(this.disposed);
- Span source = this.memory.Span.Slice(this.position);
+ Span source = this.source.Span.Slice(this.position);
this.position += source.Length;
@@ -79,13 +77,13 @@ public sealed override ValueTask WriteAsync(ReadOnlyMemory buffer, Cancell
///
public sealed override int Read(Span buffer)
{
- ValidateDisposed();
+ MemoryStream.ValidateDisposed(this.disposed);
int
- bytesAvailable = this.memory.Length - this.position,
+ bytesAvailable = this.source.Length - this.position,
bytesCopied = Math.Min(bytesAvailable, buffer.Length);
- Span source = this.memory.Span.Slice(this.position, bytesCopied);
+ Span source = this.source.Span.Slice(this.position, bytesCopied);
source.CopyTo(buffer);
@@ -97,14 +95,14 @@ public sealed override int Read(Span buffer)
///
public sealed override void Write(ReadOnlySpan buffer)
{
- ValidateDisposed();
- ValidateCanWrite();
+ MemoryStream.ValidateDisposed(this.disposed);
+ MemoryStream.ValidateCanWrite(CanWrite);
- Span destination = this.memory.Span.Slice(this.position);
+ Span destination = this.source.Span.Slice(this.position);
if (!buffer.TryCopyTo(destination))
{
- ThrowArgumentExceptionForEndOfStreamOnWrite();
+ MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.position += buffer.Length;
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs
new file mode 100644
index 00000000000..b0dcd8bc908
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs
@@ -0,0 +1,305 @@
+// 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;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Toolkit.HighPerformance.Streams
+{
+ ///
+ /// A implementation wrapping a or instance.
+ ///
+ /// The type of source to use for the underlying data.
+ ///
+ /// This type is not marked as so that it can be inherited by
+ /// , which adds the support for
+ /// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT
+ /// not being able to resolve the instruction, as this type is
+ /// only exposed as a anyway, so the generated code would be the same.
+ ///
+ internal partial class MemoryStream : Stream
+ where TSource : struct, ISpanOwner
+ {
+ ///
+ /// Indicates whether can be written to.
+ ///
+ private readonly bool isReadOnly;
+
+ ///
+ /// The instance currently in use.
+ ///
+ private TSource source;
+
+ ///
+ /// The current position within .
+ ///
+ private int position;
+
+ ///
+ /// Indicates whether or not the current instance has been disposed
+ ///
+ private bool disposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input instance to use.
+ /// Indicates whether can be written to.
+ public MemoryStream(TSource source, bool isReadOnly)
+ {
+ this.source = source;
+ this.isReadOnly = isReadOnly;
+ }
+
+ ///
+ public sealed override bool CanRead
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => !this.disposed;
+ }
+
+ ///
+ public sealed override bool CanSeek
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => !this.disposed;
+ }
+
+ ///
+ public sealed override bool CanWrite
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => !this.isReadOnly && !this.disposed;
+ }
+
+ ///
+ public sealed override long Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+
+ return this.source.Length;
+ }
+ }
+
+ ///
+ public sealed override long Position
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+
+ return this.position;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+ MemoryStream.ValidatePosition(value, this.source.Length);
+
+ this.position = unchecked((int)value);
+ }
+ }
+
+ ///
+ public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ CopyTo(destination, bufferSize);
+
+ return Task.CompletedTask;
+ }
+ catch (OperationCanceledException e)
+ {
+ return Task.FromCanceled(e.CancellationToken);
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+ }
+
+ ///
+ public sealed override void Flush()
+ {
+ }
+
+ ///
+ public sealed override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ ///
+ public sealed override Task ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ int result = Read(buffer, offset, count);
+
+ return Task.FromResult(result);
+ }
+ catch (OperationCanceledException e)
+ {
+ return Task.FromCanceled(e.CancellationToken);
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+ }
+
+ ///
+ public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
+ try
+ {
+ Write(buffer, offset, count);
+
+ return Task.CompletedTask;
+ }
+ catch (OperationCanceledException e)
+ {
+ return Task.FromCanceled(e.CancellationToken);
+ }
+ catch (Exception e)
+ {
+ return Task.FromException(e);
+ }
+ }
+
+ ///
+ public sealed override long Seek(long offset, SeekOrigin origin)
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+
+ long index = origin switch
+ {
+ SeekOrigin.Begin => offset,
+ SeekOrigin.Current => this.position + offset,
+ SeekOrigin.End => this.source.Length + offset,
+ _ => MemoryStream.ThrowArgumentExceptionForSeekOrigin()
+ };
+
+ MemoryStream.ValidatePosition(index, this.source.Length);
+
+ this.position = unchecked((int)index);
+
+ return index;
+ }
+
+ ///
+ public sealed override void SetLength(long value)
+ {
+ MemoryStream.ThrowNotSupportedExceptionForSetLength();
+ }
+
+ ///
+ public sealed override int Read(byte[]? buffer, int offset, int count)
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+ MemoryStream.ValidateBuffer(buffer, offset, count);
+
+ int
+ bytesAvailable = this.source.Length - this.position,
+ bytesCopied = Math.Min(bytesAvailable, count);
+
+ Span
+ source = this.source.Span.Slice(this.position, bytesCopied),
+ destination = buffer.AsSpan(offset, bytesCopied);
+
+ source.CopyTo(destination);
+
+ this.position += bytesCopied;
+
+ return bytesCopied;
+ }
+
+ ///
+ public sealed override int ReadByte()
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+
+ if (this.position == this.source.Length)
+ {
+ return -1;
+ }
+
+ return this.source.Span[this.position++];
+ }
+
+ ///
+ public sealed override void Write(byte[]? buffer, int offset, int count)
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+ MemoryStream.ValidateCanWrite(CanWrite);
+ MemoryStream.ValidateBuffer(buffer, offset, count);
+
+ Span
+ source = buffer.AsSpan(offset, count),
+ destination = this.source.Span.Slice(this.position);
+
+ if (!source.TryCopyTo(destination))
+ {
+ MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
+ }
+
+ this.position += source.Length;
+ }
+
+ ///
+ public sealed override void WriteByte(byte value)
+ {
+ MemoryStream.ValidateDisposed(this.disposed);
+ MemoryStream.ValidateCanWrite(CanWrite);
+
+ if (this.position == this.source.Length)
+ {
+ MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
+ }
+
+ this.source.Span[this.position++] = value;
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.disposed)
+ {
+ return;
+ }
+
+ this.disposed = true;
+ this.source = default;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs
new file mode 100644
index 00000000000..8749ab7682e
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs
@@ -0,0 +1,79 @@
+// 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;
+using System.Runtime.CompilerServices;
+#if SPAN_RUNTIME_SUPPORT
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+#endif
+
+namespace Microsoft.Toolkit.HighPerformance.Streams
+{
+ ///
+ /// An implementation wrapping an array.
+ ///
+ internal readonly struct ArrayOwner : ISpanOwner
+ {
+ ///
+ /// The wrapped array.
+ ///
+ private readonly byte[] array;
+
+ ///
+ /// The starting offset within .
+ ///
+ private readonly int offset;
+
+ ///
+ /// The usable length within .
+ ///
+ private readonly int length;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The wrapped array.
+ /// The starting offset within .
+ /// The usable length within .
+ public ArrayOwner(byte[] array, int offset, int length)
+ {
+ this.array = array;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ public static ArrayOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new ArrayOwner(Array.Empty(), 0, 0);
+ }
+
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.length;
+ }
+
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ref byte r0 = ref this.array.DangerousGetReferenceAt(this.offset);
+
+ return MemoryMarshal.CreateSpan(ref r0, this.length);
+#else
+ return this.array.AsSpan(this.offset, this.length);
+#endif
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs
new file mode 100644
index 00000000000..22fd2a77a18
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs
@@ -0,0 +1,24 @@
+// 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;
+
+namespace Microsoft.Toolkit.HighPerformance.Streams
+{
+ ///
+ /// An interface for types acting as sources for instances.
+ ///
+ internal interface ISpanOwner
+ {
+ ///
+ /// Gets the length of the underlying memory area.
+ ///
+ int Length { get; }
+
+ ///
+ /// Gets a instance wrapping the underlying memory area.
+ ///
+ Span Span { get; }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs
new file mode 100644
index 00000000000..f42eb7a1ac5
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs
@@ -0,0 +1,65 @@
+// 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;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Streams
+{
+ ///
+ /// An implementation wrapping a of instance.
+ ///
+ internal readonly struct MemoryManagerOwner : ISpanOwner
+ {
+ ///
+ /// The wrapped instance.
+ ///
+ private readonly MemoryManager memoryManager;
+
+ ///
+ /// The starting offset within .
+ ///
+ private readonly int offset;
+
+ ///
+ /// The usable length within .
+ ///
+ private readonly int length;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The wrapped instance.
+ /// The starting offset within .
+ /// The usable length within .
+ public MemoryManagerOwner(MemoryManager memoryManager, int offset, int length)
+ {
+ this.memoryManager = memoryManager;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.length;
+ }
+
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ // We can't use the same trick we use for arrays to optimize the creation of
+ // the offset span, as otherwise a bugged MemoryManager instance returning
+ // a span of an incorrect size could cause an access violation. Instead, we just
+ // get the span and then slice it, which will validate both offset and length.
+ return this.memoryManager.GetSpan().Slice(this.offset, this.length);
+ }
+ }
+ }
+}