diff --git a/Microsoft.Toolkit.HighPerformance/Box{T}.cs b/Microsoft.Toolkit.HighPerformance/Box{T}.cs index 52aaccf498d..f103f663663 100644 --- a/Microsoft.Toolkit.HighPerformance/Box{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Box{T}.cs @@ -125,7 +125,7 @@ public static bool TryGetFrom(object obj, [NotNullWhen(true)] out Box? box) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T(Box box) { - return Unsafe.Unbox(box); + return (T)(object)box; } /// @@ -180,7 +180,6 @@ public override int GetHashCode() /// /// Throws an when a cast from an invalid is attempted. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidCastExceptionForGetFrom() { throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs index 1f2b1b3e600..e17c0c48a18 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs @@ -33,6 +33,11 @@ public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner /// private const int DefaultInitialBufferSize = 256; + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -49,36 +54,52 @@ public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner /// Initializes a new instance of the class. /// public ArrayPoolBufferWriter() + : this(ArrayPool.Shared, DefaultInitialBufferSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use. + public ArrayPoolBufferWriter(ArrayPool pool) + : this(pool, DefaultInitialBufferSize) { - // Since we're using pooled arrays, we can rent the buffer with the - // default size immediately, we don't need to use lazy initialization - // to save unnecessary memory allocations in this case. - this.array = ArrayPool.Shared.Rent(DefaultInitialBufferSize); - this.index = 0; } /// /// Initializes a new instance of the class. /// /// The minimum capacity with which to initialize the underlying buffer. - /// - /// Thrown when is not positive (i.e. less than or equal to 0). - /// + /// Thrown when is not valid. public ArrayPoolBufferWriter(int initialCapacity) + : this(ArrayPool.Shared, initialCapacity) { - if (initialCapacity <= 0) - { - ThrowArgumentOutOfRangeExceptionForInitialCapacity(); - } + } - this.array = ArrayPool.Shared.Rent(initialCapacity); + /// + /// Initializes a new instance of the class. + /// + /// The instance to use. + /// The minimum capacity with which to initialize the underlying buffer. + /// Thrown when is not valid. + public ArrayPoolBufferWriter(ArrayPool pool, int initialCapacity) + { + // Since we're using pooled arrays, we can rent the buffer with the + // default size immediately, we don't need to use lazy initialization + // to save unnecessary memory allocations in this case. + // Additionally, we don't need to manually throw the exception if + // the requested size is not valid, as that'll be thrown automatically + // by the array pool in use when we try to rent an array with that size. + this.pool = pool; + this.array = pool.Rent(initialCapacity); this.index = 0; } /// /// Finalizes an instance of the class. /// - ~ArrayPoolBufferWriter() => this.Dispose(); + ~ArrayPoolBufferWriter() => Dispose(); /// Memory IMemoryOwner.Memory @@ -182,6 +203,7 @@ public void Clear() } array.AsSpan(0, this.index).Clear(); + this.index = 0; } @@ -250,7 +272,7 @@ private void CheckAndResizeBuffer(int sizeHint) { int minimumSize = this.index + sizeHint; - ArrayPool.Shared.Resize(ref this.array, minimumSize); + this.pool.Resize(ref this.array, minimumSize); } } @@ -268,7 +290,7 @@ public void Dispose() this.array = null; - ArrayPool.Shared.Return(array); + this.pool.Return(array); } /// @@ -286,19 +308,9 @@ public override string ToString() return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]"; } - /// - /// Throws an when the initial capacity is invalid. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity() - { - throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value"); - } - /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() { throw new ArgumentOutOfRangeException("count", "The count can't be a negative value"); @@ -307,7 +319,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() /// /// Throws an when the size hint is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() { throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value"); @@ -316,7 +327,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForAdvancedTooFar() { throw new ArgumentException("The buffer writer has advanced too far"); @@ -325,7 +335,6 @@ private static void ThrowArgumentExceptionForAdvancedTooFar() /// /// Throws an when is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException("The current buffer has already been disposed"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs index 2873d698c6a..9203eebc182 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs @@ -106,7 +106,7 @@ public void Advance(int count) /// public Memory GetMemory(int sizeHint = 0) { - this.ValidateSizeHint(sizeHint); + ValidateSizeHint(sizeHint); return this.memory.Slice(this.index); } @@ -114,7 +114,7 @@ public Memory GetMemory(int sizeHint = 0) /// public Span GetSpan(int sizeHint = 0) { - this.ValidateSizeHint(sizeHint); + ValidateSizeHint(sizeHint); return this.memory.Slice(this.index).Span; } @@ -159,7 +159,6 @@ public override string ToString() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() { throw new ArgumentOutOfRangeException("count", "The count can't be a negative value"); @@ -168,7 +167,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeCount() /// /// Throws an when the size hint is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() { throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value"); @@ -177,7 +175,6 @@ private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint() /// /// Throws an when the requested count is negative. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForAdvancedTooFar() { throw new ArgumentException("The buffer writer has advanced too far"); @@ -186,7 +183,6 @@ private static void ThrowArgumentExceptionForAdvancedTooFar() /// /// Throws an when the requested size exceeds the capacity. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForCapacityExceeded() { throw new ArgumentException("The buffer writer doesn't have enough capacity left"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs index 38bd7382267..b243a5fdb5d 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs @@ -32,6 +32,11 @@ public sealed class MemoryOwner : IMemoryOwner private readonly int length; #pragma warning restore IDE0032 + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -41,12 +46,14 @@ public sealed class MemoryOwner : IMemoryOwner /// Initializes a new instance of the class with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. - private MemoryOwner(int length, AllocationMode mode) + private MemoryOwner(int length, ArrayPool pool, AllocationMode mode) { this.start = 0; this.length = length; - this.array = ArrayPool.Shared.Rent(length); + this.pool = pool; + this.array = pool.Rent(length); if (mode == AllocationMode.Clear) { @@ -57,20 +64,22 @@ private MemoryOwner(int length, AllocationMode mode) /// /// Initializes a new instance of the class with the specified parameters. /// - /// The input array to use. /// The starting offset within . /// The length of the array to use. - private MemoryOwner(T[] array, int start, int length) + /// The instance currently in use. + /// The input array to use. + private MemoryOwner(int start, int length, ArrayPool pool, T[] array) { this.start = start; this.length = length; + this.pool = pool; this.array = array; } /// /// Finalizes an instance of the class. /// - ~MemoryOwner() => this.Dispose(); + ~MemoryOwner() => Dispose(); /// /// Gets an empty instance. @@ -79,7 +88,7 @@ private MemoryOwner(T[] array, int start, int length) public static MemoryOwner Empty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new MemoryOwner(0, AllocationMode.Default); + get => new MemoryOwner(0, ArrayPool.Shared, AllocationMode.Default); } /// @@ -91,19 +100,44 @@ public static MemoryOwner Empty /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryOwner Allocate(int size) => new MemoryOwner(size, AllocationMode.Default); + public static MemoryOwner Allocate(int size) => new MemoryOwner(size, ArrayPool.Shared, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// The instance currently in use. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner Allocate(int size, ArrayPool pool) => new MemoryOwner(size, pool, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// Indicates the allocation mode to use for the new buffer to rent. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner Allocate(int size, AllocationMode mode) => new MemoryOwner(size, ArrayPool.Shared, mode); /// /// Creates a new instance with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance currently in use. /// Indicates the allocation mode to use for the new buffer to rent. /// A instance of the requested length. /// Thrown when is not valid. /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryOwner Allocate(int size, AllocationMode mode) => new MemoryOwner(size, mode); + public static MemoryOwner Allocate(int size, ArrayPool pool, AllocationMode mode) => new MemoryOwner(size, pool, mode); /// /// Gets the number of items in the current instance @@ -210,7 +244,7 @@ public MemoryOwner Slice(int start, int length) ThrowInvalidLengthException(); } - return new MemoryOwner(array!, start, length); + return new MemoryOwner(start, length, this.pool, array!); } /// @@ -227,7 +261,7 @@ public void Dispose() this.array = null; - ArrayPool.Shared.Return(array); + this.pool.Return(array); } /// @@ -251,7 +285,6 @@ public override string ToString() /// /// Throws an when is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() { throw new ObjectDisposedException(nameof(MemoryOwner), "The current buffer has already been disposed"); @@ -260,7 +293,6 @@ private static void ThrowObjectDisposedException() /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOffsetException() { throw new ArgumentOutOfRangeException(nameof(start), "The input start parameter was not valid"); @@ -269,7 +301,6 @@ private static void ThrowInvalidOffsetException() /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidLengthException() { throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs index a8d3fbfb183..481f60f8195 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs @@ -42,6 +42,11 @@ public readonly ref struct SpanOwner private readonly int length; #pragma warning restore IDE0032 + /// + /// The instance used to rent . + /// + private readonly ArrayPool pool; + /// /// The underlying array. /// @@ -51,11 +56,13 @@ public readonly ref struct SpanOwner /// Initializes a new instance of the struct with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. - private SpanOwner(int length, AllocationMode mode) + private SpanOwner(int length, ArrayPool pool, AllocationMode mode) { this.length = length; - this.array = ArrayPool.Shared.Rent(length); + this.pool = pool; + this.array = pool.Rent(length); if (mode == AllocationMode.Clear) { @@ -70,7 +77,7 @@ private SpanOwner(int length, AllocationMode mode) public static SpanOwner Empty { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new SpanOwner(0, AllocationMode.Default); + get => new SpanOwner(0, ArrayPool.Shared, AllocationMode.Default); } /// @@ -82,19 +89,44 @@ public static SpanOwner Empty /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SpanOwner Allocate(int size) => new SpanOwner(size, AllocationMode.Default); + public static SpanOwner Allocate(int size) => new SpanOwner(size, ArrayPool.Shared, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// The instance to use. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner Allocate(int size, ArrayPool pool) => new SpanOwner(size, pool, AllocationMode.Default); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The length of the new memory buffer to use. + /// Indicates the allocation mode to use for the new buffer to rent. + /// A instance of the requested length. + /// Thrown when is not valid. + /// This method is just a proxy for the constructor, for clarity. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner Allocate(int size, AllocationMode mode) => new SpanOwner(size, ArrayPool.Shared, mode); /// /// Creates a new instance with the specified parameters. /// /// The length of the new memory buffer to use. + /// The instance to use. /// Indicates the allocation mode to use for the new buffer to rent. /// A instance of the requested length. /// Thrown when is not valid. /// This method is just a proxy for the constructor, for clarity. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SpanOwner Allocate(int size, AllocationMode mode) => new SpanOwner(size, mode); + public static SpanOwner Allocate(int size, ArrayPool pool, AllocationMode mode) => new SpanOwner(size, pool, mode); /// /// Gets the number of items in the current instance @@ -111,7 +143,7 @@ public int Length public Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new Span(array, 0, this.length); + get => new Span(this.array, 0, this.length); } /// @@ -122,7 +154,7 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T DangerousGetReference() { - return ref array.DangerousGetReference(); + return ref this.array.DangerousGetReference(); } /// @@ -131,7 +163,7 @@ public ref T DangerousGetReference() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { - ArrayPool.Shared.Return(array); + this.pool.Return(this.array); } /// diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs index 6cd0ceaf9f3..629d5f903f8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs @@ -205,7 +205,6 @@ public ref T Current /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn() { throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs index a0d1fe98f5c..5c146d5a5e8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs @@ -160,7 +160,6 @@ public ref T Current /// /// Throws an when the is invalid. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidRow() { throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid"); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs index 29d42924536..c732861d670 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,8 +43,9 @@ public ReadOnlySpanEnumerable(ReadOnlySpan span) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpanEnumerable GetEnumerator() => this; + public readonly ReadOnlySpanEnumerable GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -67,7 +69,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Item Current + public readonly Item Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs index 3977f5829f8..37bc6168f10 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Enumerables @@ -54,8 +55,9 @@ public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpanTokenizer GetEnumerator() => this; + public readonly ReadOnlySpanTokenizer GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -94,7 +96,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public ReadOnlySpan Current + public readonly ReadOnlySpan Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.span.Slice(this.start, this.end - this.start); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs index 791f509339d..23cbffac668 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,8 +43,9 @@ public SpanEnumerable(Span span) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanEnumerable GetEnumerator() => this; + public readonly SpanEnumerable GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -67,7 +69,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Item Current + public readonly Item Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -76,12 +78,12 @@ public Item Current ref T r0 = ref MemoryMarshal.GetReference(this.span); ref T ri = ref Unsafe.Add(ref r0, this.index); - // On .NET Standard 2.1 we can save 4 bytes by piggybacking - // the current index in the length of the wrapped span. - // We're going to use the first item as the target reference, - // and the length as a host for the current original offset. - // This is not possible on .NET Standard 2.1 as we lack - // the API to create spans from arbitrary references. + // On .NET Standard 2.1 and .NET Core (or on any target that offers runtime + // support for the Span types), we can save 4 bytes by piggybacking the + // current index in the length of the wrapped span. We're going to use the + // first item as the target reference, and the length as a host for the + // current original offset. This is not possible on eg. .NET Standard 2.0, + // as we lack the API to create Span-s from arbitrary references. return new Item(ref ri, this.index); #else return new Item(this.span, this.index); diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs index 953eb535f0b..da8a9dce040 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Enumerables @@ -54,8 +55,9 @@ public SpanTokenizer(Span span, T separator) /// Implements the duck-typed method. /// /// An instance targeting the current value. + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public SpanTokenizer GetEnumerator() => this; + public readonly SpanTokenizer GetEnumerator() => this; /// /// Implements the duck-typed method. @@ -94,7 +96,7 @@ public bool MoveNext() /// /// Gets the duck-typed property. /// - public Span Current + public readonly Span Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.span.Slice(this.start, this.end - this.start); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs index 13d15a60aaf..1f7341a93e6 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs @@ -46,7 +46,7 @@ public static void Resize(this ArrayPool pool, ref T[]? array, int newSize T[] newArray = pool.Rent(newSize); int itemsToCopy = Math.Min(array.Length, newSize); - array.AsSpan(0, itemsToCopy).CopyTo(newArray); + Array.Copy(array, 0, newArray, 0, itemsToCopy); pool.Return(array, clearArray); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs index 09a8601e484..67c30579beb 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/IBufferWriterExtensions.cs @@ -102,7 +102,6 @@ public static void Write(this IBufferWriter writer, ReadOnlySpan span) /// /// Throws an when trying to write too many bytes to the target writer. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForEndOfBuffer() { throw new ArgumentException("The current buffer writer can't contain the requested input data."); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index 9d8d6b9616a..b2a07767c01 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -164,7 +164,7 @@ public static unsafe int IndexOf(this ReadOnlySpan span, in T value) /// The value to look for. /// The number of occurrences of in . [Pure] - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Count(this ReadOnlySpan span, T value) where T : IEquatable { diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs index bb2550257ba..8ce1cca75a0 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs @@ -223,7 +223,6 @@ public static int GetDjb2HashCode(this Span span) /// /// Throws an when the given reference is out of range. /// - [MethodImpl(MethodImplOptions.NoInlining)] internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference() { throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span"); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs index 25de163cc51..331786d086d 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StreamExtensions.cs @@ -245,7 +245,6 @@ public static void Write(this Stream stream, in T value) /// /// Throws an when fails. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationExceptionForEndOfStream() { throw new InvalidOperationException("The stream didn't contain enough data to read the requested item"); diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs index 2d96413baa4..74ba2edd2fd 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs @@ -5,7 +5,9 @@ using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; +#if !NETCOREAPP3_1 using System.Runtime.InteropServices; +#endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; @@ -107,7 +109,7 @@ public static int Count(this string text, char c) /// { /// // Access the index and value of each item here... /// int index = item.Index; - /// string value = item.Value; + /// char value = item.Value; /// } /// /// The compiler will take care of properly setting up the loop with the type returned from this method. diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs index f5b0d6c1b90..b0701f932d3 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/ParallelHelper.ThrowExceptions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Helpers { @@ -15,7 +14,6 @@ public static partial class ParallelHelper /// /// Throws an when an invalid parameter is specified for the minimum actions per thread. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread() { // Having the argument name here manually typed is @@ -30,7 +28,6 @@ private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerT /// /// Throws an when an invalid start parameter is specified for 1D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd() { throw new ArgumentOutOfRangeException("start", "The start parameter must be less than or equal to end"); @@ -39,7 +36,6 @@ private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd() /// /// Throws an when a range has an index starting from an end. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name) { throw new ArgumentException("The bounds of the range can't start from an end", name); @@ -48,7 +44,6 @@ private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name) /// /// Throws an when an invalid top parameter is specified for 2D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom() { throw new ArgumentOutOfRangeException("top", "The top parameter must be less than or equal to bottom"); @@ -57,7 +52,6 @@ private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom() /// /// Throws an when an invalid left parameter is specified for 2D loops. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight() { throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right"); diff --git a/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs b/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs index 76a685d74eb..1742bfcad21 100644 --- a/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/NullableReadOnlyRef{T}.cs @@ -129,7 +129,6 @@ public static explicit operator T(NullableReadOnlyRef reference) /// /// Throws a when trying to access for a default instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationException() { throw new InvalidOperationException("The current instance doesn't have a value that can be accessed"); diff --git a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs index df4b5ef350a..d8d855799eb 100644 --- a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs @@ -113,7 +113,6 @@ public static explicit operator T(NullableRef reference) /// /// Throws a when trying to access for a default instance. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowInvalidOperationException() { throw new InvalidOperationException("The current instance doesn't have a value that can be accessed"); diff --git a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs index 1d44a481b50..209c935e4a5 100644 --- a/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs +++ b/Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.ThrowExceptions.cs @@ -4,7 +4,6 @@ using System; using System.IO; -using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.HighPerformance.Streams { @@ -16,7 +15,6 @@ internal partial class MemoryStream /// /// 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."); @@ -25,7 +23,6 @@ private static void ThrowArgumentOutOfRangeExceptionForPosition() /// /// Throws an when an input buffer is . /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentNullExceptionForBuffer() { throw new ArgumentNullException("buffer", "The buffer is null."); @@ -34,7 +31,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 +39,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 +47,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,7 +55,6 @@ 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."); @@ -70,7 +63,6 @@ private static void ThrowNotSupportedExceptionForCanWrite() /// /// 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."); @@ -79,7 +71,6 @@ private static void ThrowArgumentExceptionForEndOfStreamOnWrite() /// /// 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."); @@ -89,7 +80,6 @@ private static void ThrowNotSupportedExceptionForSetLength() /// 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"); @@ -98,7 +88,6 @@ private static long ThrowArgumentExceptionForSeekOrigin() /// /// 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"); diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs index a4943487474..e6770e01c59 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_ArrayPoolBufferWriter{T}.cs @@ -9,6 +9,7 @@ using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.Toolkit.HighPerformance.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -54,6 +55,61 @@ public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetMemoryAndSpan() Assert.ThrowsException(() => writer.Advance(1)); } + [TestCategory("ArrayPoolBufferWriterOfT")] + [TestMethod] + public void Test_ArrayPoolBufferWriterOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var writer = new ArrayPoolBufferWriter(pool)) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.AreEqual(writer.Capacity, 256); + Assert.AreEqual(writer.FreeCapacity, 256); + Assert.AreEqual(writer.WrittenCount, 0); + Assert.IsTrue(writer.WrittenMemory.IsEmpty); + Assert.IsTrue(writer.WrittenSpan.IsEmpty); + + Span span = writer.GetSpan(43); + + Assert.IsTrue(span.Length >= 43); + + writer.Advance(43); + + Assert.AreEqual(writer.Capacity, 256); + Assert.AreEqual(writer.FreeCapacity, 256 - 43); + Assert.AreEqual(writer.WrittenCount, 43); + Assert.AreEqual(writer.WrittenMemory.Length, 43); + Assert.AreEqual(writer.WrittenSpan.Length, 43); + + Assert.ThrowsException(() => writer.Advance(-1)); + Assert.ThrowsException(() => writer.GetMemory(-1)); + Assert.ThrowsException(() => writer.Advance(1024)); + + writer.Dispose(); + + Assert.ThrowsException(() => writer.WrittenMemory); + Assert.ThrowsException(() => writer.WrittenSpan.Length); + Assert.ThrowsException(() => writer.Capacity); + Assert.ThrowsException(() => writer.FreeCapacity); + Assert.ThrowsException(() => writer.Clear()); + Assert.ThrowsException(() => writer.Advance(1)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + + [TestCategory("ArrayPoolBufferWriterOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_ArrayPoolBufferWriterOfT_InvalidRequestedSize() + { + var writer = new ArrayPoolBufferWriter(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("ArrayPoolBufferWriterOfT")] [TestMethod] public void Test_ArrayPoolBufferWriterOfT_Clear() diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs index 6be4bbfa754..f4e82383356 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_MemoryOwner{T}.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -30,6 +32,39 @@ public void Test_MemoryOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("MemoryOwnerOfT")] + [TestMethod] + public void Test_MemoryOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var buffer = MemoryOwner.Allocate(127, pool)) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.IsTrue(buffer.Length == 127); + Assert.IsTrue(buffer.Memory.Length == 127); + Assert.IsTrue(buffer.Span.Length == 127); + + buffer.Span.Fill(42); + + Assert.IsTrue(buffer.Memory.Span.ToArray().All(i => i == 42)); + Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + + [TestCategory("MemoryOwnerOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_MemoryOwnerOfT_InvalidRequestedSize() + { + using var buffer = MemoryOwner.Allocate(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("MemoryOwnerOfT")] [TestMethod] [ExpectedException(typeof(ObjectDisposedException))] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs index e39de1f7a79..a79d5bb041d 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/Test_SpanOwner{T}.cs @@ -2,10 +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.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Toolkit.HighPerformance.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using UnitTests.HighPerformance.Shared.Buffers; namespace UnitTests.HighPerformance.Buffers { @@ -27,6 +29,37 @@ public void Test_SpanOwnerOfT_AllocateAndGetMemoryAndSpan() Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); } + [TestCategory("SpanOwnerOfT")] + [TestMethod] + public void Test_SpanOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan() + { + var pool = new TrackingArrayPool(); + + using (var buffer = SpanOwner.Allocate(127, pool)) + { + Assert.AreEqual(pool.RentedArrays.Count, 1); + + Assert.IsTrue(buffer.Length == 127); + Assert.IsTrue(buffer.Span.Length == 127); + + buffer.Span.Fill(42); + + Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42)); + } + + Assert.AreEqual(pool.RentedArrays.Count, 0); + } + + [TestCategory("SpanOwnerOfT")] + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void Test_SpanOwnerOfT_InvalidRequestedSize() + { + using var buffer = SpanOwner.Allocate(-1); + + Assert.Fail("You shouldn't be here"); + } + [TestCategory("HashCodeOfT")] [TestMethod] public void Test_SpanOwnerOfT_PooledBuffersAndClear() diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs new file mode 100644 index 00000000000..1ffedcefa0a --- /dev/null +++ b/UnitTests/UnitTests.HighPerformance.Shared/Buffers/TrackingArrayPool{T}.cs @@ -0,0 +1,39 @@ +// 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; + +namespace UnitTests.HighPerformance.Shared.Buffers +{ + public sealed class TrackingArrayPool : ArrayPool + { + private readonly ArrayPool pool = ArrayPool.Create(); + + private readonly HashSet arrays = new HashSet(); + + /// + /// Gets the collection of currently rented out arrays + /// + public IReadOnlyCollection RentedArrays => this.arrays; + + /// + public override T[] Rent(int minimumLength) + { + T[] array = this.pool.Rent(minimumLength); + + this.arrays.Add(array); + + return array; + } + + /// + public override void Return(T[] array, bool clearArray = false) + { + this.arrays.Remove(array); + + this.pool.Return(array, clearArray); + } + } +} diff --git a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems index 9539bf31078..ef69154b89f 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems +++ b/UnitTests/UnitTests.HighPerformance.Shared/UnitTests.HighPerformance.Shared.projitems @@ -14,6 +14,7 @@ +