From 49e504d862d33e94039c4f673f0a96f65a014859 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 00:03:20 +0200 Subject: [PATCH 01/10] Added T[] and MemoryManager owner types --- .../Streams/Sources/ArrayOwner.cs | 64 ++++++++++++++++++ .../Streams/Sources/Interfaces/ISpanOwner.cs | 11 ++++ .../Streams/Sources/MemoryManagerOwner.cs | 65 +++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs create mode 100644 Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs new file mode 100644 index 00000000000..e86aa1f3dc6 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +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; + } + + /// + 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..a384bdc93c7 --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs @@ -0,0 +1,11 @@ +using System; + +namespace Microsoft.Toolkit.HighPerformance.Streams +{ + internal interface ISpanOwner + { + int Length { get; } + + 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..9821dda128e --- /dev/null +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs @@ -0,0 +1,65 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Toolkit.HighPerformance.Extensions; + +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 + { +#if SPAN_RUNTIME_SUPPORT + ref byte r0 = ref this.memoryManager.GetSpan().DangerousGetReferenceAt(this.offset); + + return MemoryMarshal.CreateSpan(ref r0, this.length); +#else + return this.memoryManager.GetSpan(); +#endif + } + } + } +} From 20b7a950e556de64f80264bac58540b444bfc87a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 00:36:48 +0200 Subject: [PATCH 02/10] Switched MemoryStream to generic type --- .../Extensions/IMemoryOwnerExtensions.cs | 10 +- .../Extensions/MemoryExtensions.cs | 2 +- .../Extensions/ReadOnlyMemoryExtensions.cs | 4 +- .../Streams/IMemoryOwnerStream.cs | 23 +- .../Streams/MemoryStream.ThrowExceptions.cs | 75 ++--- .../Streams/MemoryStream.Validate.cs | 26 +- .../Streams/MemoryStream.cs | 314 ++---------------- ...d21.cs => MemoryStream{TSource}.Memory.cs} | 24 +- .../Streams/MemoryStream{TSource}.cs | 305 +++++++++++++++++ 9 files changed, 411 insertions(+), 372 deletions(-) rename Microsoft.Toolkit.HighPerformance/Streams/{MemoryStream.NETStandard21.cs => MemoryStream{TSource}.Memory.cs} (80%) create mode 100644 Microsoft.Toolkit.HighPerformance/Streams/MemoryStream{TSource}.cs diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs index fbcef472cb5..59b73fe8f84 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs @@ -6,7 +6,7 @@ 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 +18,17 @@ 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. /// [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..3e215d88bcd 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -30,7 +30,7 @@ public static class MemoryExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this Memory memory) { - return new MemoryStream(memory); + return MemoryStream.Create(memory, true); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index 46101e3db89..89dcd00dc86 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 @@ -27,10 +26,9 @@ public static class ReadOnlyMemoryExtensions /// as the returned is in use, to avoid unexpected issues. /// [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this ReadOnlyMemory memory) { - return new MemoryStream(memory); + return MemoryStream.Create(memory, false); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs index c9a0a428e4b..22bab320ac5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.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,25 @@ 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. + /// Indicates whether can be written to. + /// The instance currently in use. + public IMemoryOwnerStream(TSource source, bool isReadOnly, IDisposable disposable) + : base(source, isReadOnly) { - this.memory = memory; + this.disposable = disposable; } /// @@ -32,7 +37,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 1d44a481b50..a8a86483859 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs @@ -1,31 +1,49 @@ -// 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; using System.IO; -using System.Runtime.CompilerServices; 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. /// - [MethodImpl(MethodImplOptions.NoInlining)] 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."); } /// /// Throws an when an input buffer is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentNullExceptionForBuffer() { throw new ArgumentNullException("buffer", "The buffer is null."); @@ -34,7 +52,6 @@ private static void ThrowArgumentNullExceptionForBuffer() /// /// Throws an when the input count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForOffset() { throw new ArgumentOutOfRangeException("offset", "Offset can't be negative."); @@ -43,7 +60,6 @@ private static void ThrowArgumentOutOfRangeExceptionForOffset() /// /// Throws an when the input count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForCount() { throw new ArgumentOutOfRangeException("count", "Count can't be negative."); @@ -52,7 +68,6 @@ private static void ThrowArgumentOutOfRangeExceptionForCount() /// /// Throws an when the sum of offset and count exceeds the length of the target buffer. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForLength() { throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer"); @@ -61,47 +76,17 @@ private static void ThrowArgumentExceptionForLength() /// /// Throws a when trying to write on a readonly stream. /// - [MethodImpl(MethodImplOptions.NoInlining)] 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. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - 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. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - 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. - [MethodImpl(MethodImplOptions.NoInlining)] - private static long ThrowArgumentExceptionForSeekOrigin() - { - throw new ArgumentException("The input seek mode is not valid.", "origin"); - } - /// /// Throws an when using a disposed instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] 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..95f4bd538fb 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs @@ -1,16 +1,12 @@ -// 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.IO; +using System.IO; using System.Runtime.CompilerServices; 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 +14,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 +29,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 +53,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..f1607634641 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -1,315 +1,67 @@ -// 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; +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. - /// - 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. + /// Creates a new from the input of instance. /// - /// 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) - { - 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) + /// The input instance. + /// Indicates whether or not can be written to. + /// A wrapping the underlying data for . + [Pure] + public static Stream Create(ReadOnlyMemory memory, bool isReadOnly) { - 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) + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { - return Task.FromException(e); - } - } + var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); - /// - public sealed override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); + return new MemoryStream(arraySpanSource, isReadOnly); } - try + if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length)) { - Write(buffer, offset, count); + MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length); - return Task.CompletedTask; - } - catch (OperationCanceledException e) - { - return Task.FromCanceled(e.CancellationToken); + return new MemoryStream(memoryManagerSpanSource, isReadOnly); } - catch (Exception e) - { - return Task.FromException(e); - } - } - - /// - public sealed override long Seek(long offset, SeekOrigin origin) - { - ValidateDisposed(); - - long index = origin switch - { - 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(); + throw new NotImplementedException(); } - /// - 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; - - return bytesCopied; - } - - /// - public sealed override int ReadByte() + /// + /// Creates a new from the input of instance. + /// + /// The input instance. + /// A wrapping the underlying data for . + [Pure] + public static Stream Create(IMemoryOwner memoryOwner) { - ValidateDisposed(); + Memory memory = memoryOwner.Memory; - if (this.position == this.memory.Length) + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { - return -1; - } + var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); - 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)) - { - ThrowArgumentExceptionForEndOfStreamOnWrite(); + return new IMemoryOwnerStream(arraySpanSource, false, memoryOwner); } - this.position += source.Length; - } - - /// - public sealed override void WriteByte(byte value) - { - ValidateDisposed(); - ValidateCanWrite(); - - if (this.position == this.memory.Length) + if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length)) { - ThrowArgumentExceptionForEndOfStreamOnWrite(); - } + MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length); - this.memory.Span[this.position++] = value; - } - - /// - protected override void Dispose(bool disposing) - { - if (this.disposed) - { - return; + return new IMemoryOwnerStream(memoryManagerSpanSource, false, memoryOwner); } - this.disposed = true; - this.memory = default; + throw new NotImplementedException(); } } } 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; + } + } +} From ad2951e4e7b6daa689a36d3cc774dcff36971fe3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 00:59:57 +0200 Subject: [PATCH 03/10] Added empty stream as fallback path --- .../Streams/MemoryStream.cs | 6 ++++-- .../Streams/Sources/ArrayOwner.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs index f1607634641..959d160beb7 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -34,7 +34,8 @@ public static Stream Create(ReadOnlyMemory memory, bool isReadOnly) return new MemoryStream(memoryManagerSpanSource, isReadOnly); } - throw new NotImplementedException(); + // Return an empty stream if the memory was empty + return new MemoryStream(ArrayOwner.Empty, isReadOnly); } /// @@ -61,7 +62,8 @@ public static Stream Create(IMemoryOwner memoryOwner) return new IMemoryOwnerStream(memoryManagerSpanSource, false, memoryOwner); } - throw new NotImplementedException(); + // Return an empty stream if the memory was empty + return new IMemoryOwnerStream(ArrayOwner.Empty, false, memoryOwner); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs index e86aa1f3dc6..e644c1da2a9 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs @@ -38,6 +38,15 @@ public ArrayOwner(byte[] array, int offset, int length) this.length = length; } + /// + /// Gets an empty instance. + /// + public static ArrayOwner Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ArrayOwner(Array.Empty(), 0, 0); + } + /// public int Length { From aeb8fd54f006521ec8addef47bb0f486fd82e0e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 01:13:38 +0200 Subject: [PATCH 04/10] Fixed inverted readonly setting for MemoryStream --- .../Extensions/MemoryExtensions.cs | 2 +- .../Extensions/ReadOnlyMemoryExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 3e215d88bcd..44b496170f5 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -30,7 +30,7 @@ public static class MemoryExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stream AsStream(this Memory memory) { - return MemoryStream.Create(memory, true); + return MemoryStream.Create(memory, false); } } } diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index 89dcd00dc86..0639ae39c5f 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs @@ -28,7 +28,7 @@ public static class ReadOnlyMemoryExtensions [Pure] public static Stream AsStream(this ReadOnlyMemory memory) { - return MemoryStream.Create(memory, false); + return MemoryStream.Create(memory, true); } } } From 045aeb7f5fe4d505de9bca34bf53990cc7c9e758 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 01:17:59 +0200 Subject: [PATCH 05/10] Added missing file headers --- .../Streams/MemoryStream.ThrowExceptions.cs | 6 +++++- .../Streams/MemoryStream.Validate.cs | 6 +++++- Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs | 6 +++++- .../Streams/Sources/ArrayOwner.cs | 6 +++++- .../Streams/Sources/Interfaces/ISpanOwner.cs | 6 +++++- .../Streams/Sources/MemoryManagerOwner.cs | 6 +++++- 6 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs index a8a86483859..cdfc24112d5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs @@ -1,4 +1,8 @@ -using System; +// 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; namespace Microsoft.Toolkit.HighPerformance.Streams diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs index 95f4bd538fb..9cd0ff62936 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs @@ -1,4 +1,8 @@ -using System.IO; +// 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.IO; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Streams diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs index 959d160beb7..13872403bdf 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -1,4 +1,8 @@ -using System; +// 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; diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs index e644c1da2a9..d2d2594b5b2 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs @@ -1,4 +1,8 @@ -using System; +// 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; using System.Runtime.InteropServices; using Microsoft.Toolkit.HighPerformance.Extensions; diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs index a384bdc93c7..b962cd48a3b 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs @@ -1,4 +1,8 @@ -using System; +// 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 { diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs index 9821dda128e..82b07c19bbe 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs @@ -1,4 +1,8 @@ -using System; +// 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; using System.Runtime.InteropServices; From 6efa7847c785ca4c0e23b5fc1b47230c0092d714 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 01:47:50 +0200 Subject: [PATCH 06/10] Fixed potential access violation with faulty MemoryManager-s --- .../Streams/Sources/ArrayOwner.cs | 2 ++ .../Streams/Sources/MemoryManagerOwner.cs | 14 +++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs index d2d2594b5b2..8749ab7682e 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/ArrayOwner.cs @@ -4,8 +4,10 @@ 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 { diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs index 82b07c19bbe..f42eb7a1ac5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/MemoryManagerOwner.cs @@ -5,8 +5,6 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.Toolkit.HighPerformance.Extensions; namespace Microsoft.Toolkit.HighPerformance.Streams { @@ -56,13 +54,11 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if SPAN_RUNTIME_SUPPORT - ref byte r0 = ref this.memoryManager.GetSpan().DangerousGetReferenceAt(this.offset); - - return MemoryMarshal.CreateSpan(ref r0, this.length); -#else - return this.memoryManager.GetSpan(); -#endif + // 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); } } } From 832a3687cb27038143a2aeaad760446b4717583c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 23:14:11 +0200 Subject: [PATCH 07/10] Renamed file to match class name --- .../{IMemoryOwnerStream.cs => IMemoryOwnerStream{TSource}.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Microsoft.Toolkit.HighPerformance/Streams/{IMemoryOwnerStream.cs => IMemoryOwnerStream{TSource}.cs} (100%) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs similarity index 100% rename from Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream.cs rename to Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs From 5aa2dfa93bb4fd6e03e0eef181e1b14fbae4955e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 16 Sep 2020 23:19:33 +0200 Subject: [PATCH 08/10] Added missing XML comments --- .../Streams/Sources/Interfaces/ISpanOwner.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs index b962cd48a3b..22fd2a77a18 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/Sources/Interfaces/ISpanOwner.cs @@ -6,10 +6,19 @@ 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; } } } From fb0dbc7a06f27c4529b78235fa95be251f387d4c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 17 Sep 2020 01:20:35 +0200 Subject: [PATCH 09/10] Removed unnecessary constructor parameter --- .../Streams/IMemoryOwnerStream{TSource}.cs | 5 ++--- Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs index 22bab320ac5..2dfe66e215e 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/IMemoryOwnerStream{TSource}.cs @@ -24,10 +24,9 @@ internal sealed class IMemoryOwnerStream : MemoryStream /// Initializes a new instance of the class. /// /// The input instance to use. - /// Indicates whether can be written to. /// The instance currently in use. - public IMemoryOwnerStream(TSource source, bool isReadOnly, IDisposable disposable) - : base(source, isReadOnly) + public IMemoryOwnerStream(TSource source, IDisposable disposable) + : base(source, false) { this.disposable = disposable; } diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs index 13872403bdf..247fb3833ac 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -56,18 +56,18 @@ public static Stream Create(IMemoryOwner memoryOwner) { var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); - return new IMemoryOwnerStream(arraySpanSource, false, memoryOwner); + return new IMemoryOwnerStream(arraySpanSource, memoryOwner); } if (MemoryMarshal.TryGetMemoryManager>(memory, out var memoryManager, out int start, out int length)) { MemoryManagerOwner memoryManagerSpanSource = new MemoryManagerOwner(memoryManager, start, length); - return new IMemoryOwnerStream(memoryManagerSpanSource, false, memoryOwner); + return new IMemoryOwnerStream(memoryManagerSpanSource, memoryOwner); } // Return an empty stream if the memory was empty - return new IMemoryOwnerStream(ArrayOwner.Empty, false, memoryOwner); + return new IMemoryOwnerStream(ArrayOwner.Empty, memoryOwner); } } } From c01fe8f2fc918f17921fb6e91ff546a4f9ffed19 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 18 Sep 2020 21:59:53 +0200 Subject: [PATCH 10/10] Code tweaks to MemoryStream initialization --- .../Extensions/IMemoryOwnerExtensions.cs | 2 ++ .../Extensions/MemoryExtensions.cs | 1 + .../Extensions/ReadOnlyMemoryExtensions.cs | 1 + .../Streams/MemoryStream.cs | 30 ++++++++++++++++--- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs index 59b73fe8f84..cb4ec0f2829 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.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.Diagnostics.Contracts; using System.IO; @@ -24,6 +25,7 @@ public static class IMemoryOwnerExtensions /// 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 memoryOwner) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs index 44b496170f5..794339fbbed 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs @@ -26,6 +26,7 @@ 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) diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs index 0639ae39c5f..eab50592776 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs @@ -25,6 +25,7 @@ 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] public static Stream AsStream(this ReadOnlyMemory memory) { diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs index 247fb3833ac..cd2908bd881 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.cs @@ -21,9 +21,16 @@ internal static partial class MemoryStream /// 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 (memory.IsEmpty) + { + // Return an empty stream if the memory was empty + return new MemoryStream(ArrayOwner.Empty, isReadOnly); + } + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); @@ -38,8 +45,7 @@ public static Stream Create(ReadOnlyMemory memory, bool isReadOnly) return new MemoryStream(memoryManagerSpanSource, isReadOnly); } - // Return an empty stream if the memory was empty - return new MemoryStream(ArrayOwner.Empty, isReadOnly); + return ThrowNotSupportedExceptionForInvalidMemory(); } /// @@ -47,11 +53,18 @@ public static Stream Create(ReadOnlyMemory memory, bool isReadOnly) /// /// The input instance. /// A wrapping the underlying data for . + /// Thrown when has an invalid data store. [Pure] public static Stream Create(IMemoryOwner memoryOwner) { Memory memory = memoryOwner.Memory; + if (memory.IsEmpty) + { + // Return an empty stream if the memory was empty + return new IMemoryOwnerStream(ArrayOwner.Empty, memoryOwner); + } + if (MemoryMarshal.TryGetArray(memory, out ArraySegment segment)) { var arraySpanSource = new ArrayOwner(segment.Array!, segment.Offset, segment.Count); @@ -66,8 +79,17 @@ public static Stream Create(IMemoryOwner memoryOwner) return new IMemoryOwnerStream(memoryManagerSpanSource, memoryOwner); } - // Return an empty stream if the memory was empty - return new IMemoryOwnerStream(ArrayOwner.Empty, memoryOwner); + return ThrowNotSupportedExceptionForInvalidMemory(); + } + + /// + /// Throws a when a given + /// or instance has an unsupported backing store. + /// + /// Nothing, this method always throws. + private static Stream ThrowNotSupportedExceptionForInvalidMemory() + { + throw new ArgumentException("The input instance doesn't have a valid underlying data store."); } } }