diff --git a/Microsoft.Toolkit.HighPerformance/Box{T}.cs b/Microsoft.Toolkit.HighPerformance/Box{T}.cs
new file mode 100644
index 00000000000..c98de01e580
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Box{T}.cs
@@ -0,0 +1,219 @@
+// 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.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance
+{
+ ///
+ /// A that represents a boxed value on the managed heap.
+ ///
+ /// The type of value being boxed.
+ [DebuggerDisplay("{ToString(),raw}")]
+ public sealed class Box
+ where T : struct
+ {
+ // Boxed value types in the CLR are represented in memory as simple objects that store the method table of
+ // the corresponding T value type being boxed, and then the data of the value being boxed:
+ // [ sync block || pMethodTable || boxed T value ]
+ // ^ ^
+ // | \-- Unsafe.Unbox(Box)
+ // \-- Box reference
+ // For more info, see: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/.
+ // Note that there might be some padding before the actual data representing the boxed value,
+ // which might depend on both the runtime and the exact CPU architecture.
+ // This is automatically handled by the unbox !!T instruction in IL, which
+ // unboxes a given value type T and returns a reference to its boxed data.
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// This constructor is never used, it is only declared in order to mark it with
+ /// the visibility modifier and prevent direct use.
+ ///
+ private Box()
+ {
+ }
+
+ ///
+ /// Returns a reference from the input instance.
+ ///
+ /// The input instance, representing a boxed value.
+ /// A reference pointing to .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Box GetFrom(object obj)
+ {
+ if (obj.GetType() != typeof(T))
+ {
+ ThrowInvalidCastExceptionForGetFrom();
+ }
+
+ return Unsafe.As>(obj);
+ }
+
+ ///
+ /// Returns a reference from the input instance.
+ ///
+ /// The input instance, representing a boxed value.
+ /// A reference pointing to .
+ ///
+ /// This method doesn't check the actual type of , so it is responsability of the caller
+ /// to ensure it actually represents a boxed value and not some other instance.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Box DangerousGetFrom(object obj)
+ {
+ return Unsafe.As>(obj);
+ }
+
+ ///
+ /// Tries to get a reference from an input representing a boxed value.
+ ///
+ /// The input instance to check.
+ /// The resulting reference, if was a boxed value.
+ /// if a instance was retrieved correctly, otherwise.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1115", Justification = "Comment for [NotNullWhen] attribute")]
+ public static bool TryGetFrom(
+ object obj,
+#if NETSTANDARD2_1
+ /* On .NET Standard 2.1, we can add the [NotNullWhen] attribute
+ * to let the code analysis engine know that whenever this method
+ * returns true, box will always be assigned to a non-null value.
+ * This will eliminate the null warnings when in a branch that
+ * is only taken when this method returns true. */
+ [NotNullWhen(true)]
+#endif
+ out Box? box)
+ {
+ if (obj.GetType() == typeof(T))
+ {
+ box = Unsafe.As>(obj);
+
+ return true;
+ }
+
+ box = null;
+
+ return false;
+ }
+
+ ///
+ /// Implicitly gets the value from a given instance.
+ ///
+ /// The input instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator T(Box box)
+ {
+ return Unsafe.Unbox(box);
+ }
+
+ ///
+ /// Implicitly creates a new instance from a given value.
+ ///
+ /// The input value to wrap.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Box(T value)
+ {
+ /* The Box type is never actually instantiated.
+ * Here we are just boxing the input T value, and then reinterpreting
+ * that object reference as a Box reference. As such, the Box
+ * type is really only used as an interface to access the contents
+ * of a boxed value type. This also makes it so that additional methods
+ * like ToString() or GetHashCode() will automatically be referenced from
+ * the method table of the boxed object, meaning that they don't need to
+ * manually be implemented in the Box type. For instance, boxing a float
+ * and calling ToString() on it directly, on its boxed object or on a Box
+ * reference retrieved from it will produce the same result in all cases. */
+ return Unsafe.As>(value);
+ }
+
+ ///
+ public override string ToString()
+ {
+ /* Here we're overriding the base object virtual methods to ensure
+ * calls to those methods have a correct results on all runtimes.
+ * For instance, not doing so is causing issue on .NET Core 2.1 Release
+ * due to how the runtime handles the Box reference to an actual
+ * boxed T value (not a concrete Box instance as it would expect).
+ * To fix that, the overrides will simply call the expected methods
+ * directly on the boxed T values. These methods will be directly
+ * invoked by the JIT compiler when using a Box reference. When
+ * an object reference is used instead, the call would be forwarded
+ * to those same methods anyway, since the method table for an object
+ * representing a T instance is the one of type T anyway. */
+ return this.GetReference().ToString();
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(this, obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.GetReference().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)}>");
+ }
+ }
+
+ ///
+ /// Helpers for working with the type.
+ ///
+ [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402", Justification = "Extension class to replace instance methods for Box")]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Extensions being declared after the type they apply to")]
+ public static class BoxExtensions
+ {
+ ///
+ /// Gets a reference from a instance.
+ ///
+ /// The type of reference to retrieve.
+ /// The input instance.
+ /// A reference to the boxed value within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetReference(this Box box)
+ where T : struct
+ {
+ /* The reason why this method is an extension and is not part of
+ * the Box type itself is that Box is really just a mask
+ * used over object references, but it is never actually instantiated.
+ * Because of this, the method table of the objects in the heap will
+ * be the one of type T created by the runtime, and not the one of
+ * the Box type. To avoid potential issues when invoking this method
+ * on different runtimes, which might handle that scenario differently,
+ * we use an extension method, which is just syntactic sugar for a static
+ * method belonging to another class. This isn't technically necessary,
+ * but it's just an extra precaution since the syntax for users remains
+ * exactly the same anyway. Here we just call the Unsafe.Unbox(object)
+ * API, which is hidden away for users of the type for simplicity.
+ * Note that this API will always actually involve a conditional
+ * branch, which is introduced by the JIT compiler to validate the
+ * object instance being unboxed. But since the alternative of
+ * manually tracking the offset to the boxed data would be both
+ * more error prone, and it would still introduce some overhead,
+ * this doesn't really matter in this case anyway. */
+ return ref Unsafe.Unbox(box);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs
new file mode 100644
index 00000000000..dbed50f49c1
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs
@@ -0,0 +1,353 @@
+// 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;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Views;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers
+{
+ ///
+ /// Represents a heap-based, array-backed output sink into which data can be written.
+ ///
+ /// The type of items to write to the current instance.
+ ///
+ /// This is a custom implementation that replicates the
+ /// functionality and API surface of the array-based buffer writer available in
+ /// .NET Standard 2.1, with the main difference being the fact that in this case
+ /// the arrays in use are rented from the shared instance,
+ /// and that is also available on .NET Standard 2.0.
+ ///
+ [DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public sealed class ArrayPoolBufferWriter : IBufferWriter, IMemoryOwner
+ {
+ ///
+ /// The default buffer size to use to expand empty arrays.
+ ///
+ private const int DefaultInitialBufferSize = 256;
+
+ ///
+ /// The underlying array.
+ ///
+ private T[]? array;
+
+#pragma warning disable IDE0032
+ ///
+ /// The starting offset within .
+ ///
+ private int index;
+#pragma warning restore IDE0032
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ArrayPoolBufferWriter()
+ {
+ /* 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).
+ ///
+ public ArrayPoolBufferWriter(int initialCapacity)
+ {
+ if (initialCapacity <= 0)
+ {
+ ThrowArgumentOutOfRangeExceptionForInitialCapacity();
+ }
+
+ this.array = ArrayPool.Shared.Rent(initialCapacity);
+ this.index = 0;
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~ArrayPoolBufferWriter() => this.Dispose();
+
+ ///
+ Memory IMemoryOwner.Memory
+ {
+ /* This property is explicitly implemented so that it's hidden
+ * under normal usage, as the name could be confusing when
+ * displayed besides WrittenMemory and GetMemory().
+ * The IMemoryOwner interface is implemented primarily
+ * so that the AsStream() extension can be used on this type,
+ * allowing users to first create a ArrayPoolBufferWriter
+ * instance to write data to, then get a stream through the
+ * extension and let it take care of returning the underlying
+ * buffer to the shared pool when it's no longer necessary.
+ * Inlining is not needed here since this will always be a callvirt. */
+ get => MemoryMarshal.AsMemory(WrittenMemory);
+ }
+
+ ///
+ /// Gets the data written to the underlying buffer so far, as a .
+ ///
+ public ReadOnlyMemory WrittenMemory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return array!.AsMemory(0, this.index);
+ }
+ }
+
+ ///
+ /// Gets the data written to the underlying buffer so far, as a .
+ ///
+ public ReadOnlySpan WrittenSpan
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return array!.AsSpan(0, this.index);
+ }
+ }
+
+ ///
+ /// Gets the amount of data written to the underlying buffer so far.
+ ///
+ public int WrittenCount
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.index;
+ }
+
+ ///
+ /// Gets the total amount of space within the underlying buffer.
+ ///
+ public int Capacity
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return array!.Length;
+ }
+ }
+
+ ///
+ /// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
+ ///
+ public int FreeCapacity
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return array!.Length - this.index;
+ }
+ }
+
+ ///
+ /// Clears the data written to the underlying buffer.
+ ///
+ ///
+ /// You must clear the before trying to re-use it.
+ ///
+ public void Clear()
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ array.AsSpan(0, this.index).Clear();
+ this.index = 0;
+ }
+
+ ///
+ public void Advance(int count)
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ if (count < 0)
+ {
+ ThrowArgumentOutOfRangeExceptionForNegativeCount();
+ }
+
+ if (this.index > array!.Length - count)
+ {
+ ThrowArgumentExceptionForAdvancedTooFar();
+ }
+
+ this.index += count;
+ }
+
+ ///
+ public Memory GetMemory(int sizeHint = 0)
+ {
+ CheckAndResizeBuffer(sizeHint);
+
+ return this.array.AsMemory(this.index);
+ }
+
+ ///
+ public Span GetSpan(int sizeHint = 0)
+ {
+ CheckAndResizeBuffer(sizeHint);
+
+ return this.array.AsSpan(this.index);
+ }
+
+ ///
+ /// Ensures that has enough free space to contain a given number of new items.
+ ///
+ /// The minimum number of items to ensure space for in .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CheckAndResizeBuffer(int sizeHint)
+ {
+ if (this.array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ if (sizeHint < 0)
+ {
+ ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
+ }
+
+ if (sizeHint == 0)
+ {
+ sizeHint = 1;
+ }
+
+ if (sizeHint > FreeCapacity)
+ {
+ int minimumSize = this.index + sizeHint;
+
+ ArrayPool.Shared.Resize(ref this.array, minimumSize);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ return;
+ }
+
+ GC.SuppressFinalize(this);
+
+ this.array = null;
+
+ ArrayPool.Shared.Return(array);
+ }
+
+ ///
+ [Pure]
+ public override string ToString()
+ {
+ // See comments in MemoryOwner about this
+ if (typeof(T) == typeof(char) &&
+ this.array is char[] chars)
+ {
+ return new string(chars, 0, this.index);
+ }
+
+ // Same representation used in Span
+ return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
+ }
+
+ ///
+ /// Throws an when the initial capacity is invalid.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
+ 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");
+ }
+
+ ///
+ /// 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");
+ }
+
+ ///
+ /// 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");
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException("The current buffer has already been disposed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Enums/AllocationMode.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Enums/AllocationMode.cs
new file mode 100644
index 00000000000..08663ba76b8
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Enums/AllocationMode.cs
@@ -0,0 +1,22 @@
+// 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.
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers
+{
+ ///
+ /// An that indicates a mode to use when allocating buffers.
+ ///
+ public enum AllocationMode
+ {
+ ///
+ /// The default allocation mode for pooled memory (rented buffers are not cleared).
+ ///
+ Default,
+
+ ///
+ /// Clear pooled buffers when renting them.
+ ///
+ Clear
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs
new file mode 100644
index 00000000000..626293d5b85
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs
@@ -0,0 +1,282 @@
+// 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;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Views;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers
+{
+ ///
+ /// An implementation with an embedded length and a fast accessor.
+ ///
+ /// The type of items to store in the current instance.
+ [DebuggerTypeProxy(typeof(MemoryOwnerDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ public sealed class MemoryOwner : IMemoryOwner
+ {
+ ///
+ /// The starting offset within .
+ ///
+ private readonly int start;
+
+#pragma warning disable IDE0032
+ ///
+ /// The usable length within (starting from ).
+ ///
+ private readonly int length;
+#pragma warning restore IDE0032
+
+ ///
+ /// The underlying array.
+ ///
+ private T[]? array;
+
+ ///
+ /// Initializes a new instance of the class 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.
+ private MemoryOwner(int length, AllocationMode mode)
+ {
+ this.start = 0;
+ this.length = length;
+ this.array = ArrayPool.Shared.Rent(length);
+
+ if (mode == AllocationMode.Clear)
+ {
+ this.array.AsSpan(0, length).Clear();
+ }
+ }
+
+ ///
+ /// 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)
+ {
+ this.start = start;
+ this.length = length;
+ this.array = array;
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~MemoryOwner() => this.Dispose();
+
+ ///
+ /// Gets an empty instance.
+ ///
+ [Pure]
+ public static MemoryOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new MemoryOwner(0, AllocationMode.Default);
+ }
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// The length of the new memory buffer 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 MemoryOwner Allocate(int size) => new MemoryOwner(size, 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, mode);
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.length;
+ }
+
+ ///
+ public Memory Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new Memory(array!, this.start, this.length);
+ }
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new Span(array!, this.start, this.length);
+ }
+ }
+
+ ///
+ /// Returns a reference to the first element within the current instance, with no bounds check.
+ ///
+ /// A reference to the first element within the current instance.
+ /// Thrown when the buffer in use has already been disposed.
+ ///
+ /// This method does not perform bounds checks on the underlying buffer, but does check whether
+ /// the buffer itself has been disposed or not. This check should not be removed, and it's also
+ /// the reason why the method to get a reference at a specified offset is not present.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T DangerousGetReference()
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return ref array!.DangerousGetReferenceAt(this.start);
+ }
+
+ ///
+ /// Slices the buffer currently in use and returns a new instance.
+ ///
+ /// The starting offset within the current buffer.
+ /// The length of the buffer to use.
+ /// A new instance using the target range of items.
+ /// Thrown when the buffer in use has already been disposed.
+ /// Thrown when or are not valid.
+ ///
+ /// Using this method will dispose the current instance, and should only be used when an oversized
+ /// buffer is rented and then adjusted in size, to avoid having to rent a new buffer of the new
+ /// size and copy the previous items into the new one, or needing an additional variable/field
+ /// to manually handle to track the used range within a given instance.
+ ///
+ [Pure]
+ public MemoryOwner Slice(int start, int length)
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ this.array = null;
+
+ if ((uint)start > this.length)
+ {
+ ThrowInvalidOffsetException();
+ }
+
+ if ((uint)length > (this.length - start))
+ {
+ ThrowInvalidLengthException();
+ }
+
+ return new MemoryOwner(array!, start, length);
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = this.array;
+
+ if (array is null)
+ {
+ return;
+ }
+
+ GC.SuppressFinalize(this);
+
+ this.array = null;
+
+ ArrayPool.Shared.Return(array);
+ }
+
+ ///
+ [Pure]
+ public override string ToString()
+ {
+ /* Normally we would throw if the array has been disposed,
+ * but in this case we'll just return the non formatted
+ * representation as a fallback, since the ToString method
+ * is generally expected not to throw exceptions. */
+ if (typeof(T) == typeof(char) &&
+ this.array is char[] chars)
+ {
+ return new string(chars, this.start, this.length);
+ }
+
+ // Same representation used in Span
+ return $"Microsoft.Toolkit.HighPerformance.Buffers.MemoryOwner<{typeof(T)}>[{this.length}]";
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryOwner), "The current buffer has already been disposed");
+ }
+
+ ///
+ /// 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");
+ }
+
+ ///
+ /// 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
new file mode 100644
index 00000000000..c4a24758103
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs
@@ -0,0 +1,155 @@
+// 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;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Views;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers
+{
+ ///
+ /// A stack-only type with the ability to rent a buffer of a specified length and getting a from it.
+ /// This type mirrors but without allocations and with further optimizations.
+ /// As this is a stack-only type, it relies on the duck-typed pattern introduced with C# 8.
+ /// It should be used like so:
+ ///
+ /// using (SpanOwner<byte> buffer = SpanOwner<byte>.Allocate(1024))
+ /// {
+ /// // Use the buffer here...
+ /// }
+ ///
+ /// As soon as the code leaves the scope of that block, the underlying buffer will automatically
+ /// be disposed. The APIs in rely on this pattern for extra performance, eg. they don't perform
+ /// the additional checks that are done in to ensure that the buffer hasn't been disposed
+ /// before returning a or instance from it.
+ /// As such, this type should always be used with a block or expression.
+ /// Not doing so will cause the underlying buffer not to be returned to the shared pool.
+ ///
+ /// The type of items to store in the current instance.
+ [DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
+ [DebuggerDisplay("{ToString(),raw}")]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ public readonly ref struct SpanOwner
+ {
+#pragma warning disable IDE0032
+ ///
+ /// The usable length within .
+ ///
+ private readonly int length;
+#pragma warning restore IDE0032
+
+ ///
+ /// The underlying array.
+ ///
+ private readonly T[] array;
+
+ ///
+ /// Initializes a new instance of the struct 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.
+ private SpanOwner(int length, AllocationMode mode)
+ {
+ this.length = length;
+ this.array = ArrayPool.Shared.Rent(length);
+
+ if (mode == AllocationMode.Clear)
+ {
+ this.array.AsSpan(0, length).Clear();
+ }
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ [Pure]
+ public static SpanOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new SpanOwner(0, AllocationMode.Default);
+ }
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// The length of the new memory buffer 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) => new SpanOwner(size, 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, mode);
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.length;
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new Span(array, 0, this.length);
+ }
+
+ ///
+ /// Returns a reference to the first element within the current instance, with no bounds check.
+ ///
+ /// A reference to the first element within the current instance.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T DangerousGetReference()
+ {
+ return ref array.DangerousGetReference();
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(array);
+ }
+
+ ///
+ [Pure]
+ public override string ToString()
+ {
+ if (typeof(T) == typeof(char) &&
+ this.array is char[] chars)
+ {
+ return new string(chars, 0, this.length);
+ }
+
+ // Same representation used in Span
+ return $"Microsoft.Toolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs
new file mode 100644
index 00000000000..8518c4d3901
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs
@@ -0,0 +1,32 @@
+// 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.Diagnostics;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
+{
+ ///
+ /// A debug proxy used to display items for the type.
+ ///
+ /// The type of items stored in the input instances..
+ internal sealed class ArrayPoolBufferWriterDebugView
+ {
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter? arrayPoolBufferWriter)
+ {
+ this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
+ }
+
+ ///
+ /// Gets the items to display for the current instance
+ ///
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[]? Items { get; }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs
new file mode 100644
index 00000000000..f63e94f2740
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs
@@ -0,0 +1,32 @@
+// 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.Diagnostics;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
+{
+ ///
+ /// A debug proxy used to display items for the type.
+ ///
+ /// The type of items stored in the input instances..
+ internal sealed class MemoryOwnerDebugView
+ {
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public MemoryOwnerDebugView(MemoryOwner? memoryOwner)
+ {
+ this.Items = memoryOwner?.Span.ToArray();
+ }
+
+ ///
+ /// Gets the items to display for the current instance
+ ///
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[]? Items { get; }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs
new file mode 100644
index 00000000000..a945769f01a
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs
@@ -0,0 +1,32 @@
+// 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.Diagnostics;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
+{
+ ///
+ /// A debug proxy used to display items for the type.
+ ///
+ /// The type of items stored in the input instances..
+ internal sealed class SpanOwnerDebugView
+ {
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public SpanOwnerDebugView(SpanOwner spanOwner)
+ {
+ this.Items = spanOwner.Span.ToArray();
+ }
+
+ ///
+ /// Gets the items to display for the current instance
+ ///
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[]? Items { get; }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs
new file mode 100644
index 00000000000..2698a2029f4
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs
@@ -0,0 +1,163 @@
+// 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.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that iterates a column in a given 2D array instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct Array2DColumnEnumerable
+ {
+ ///
+ /// The source 2D array instance.
+ ///
+ private readonly T[,] array;
+
+ ///
+ /// The target column to iterate within .
+ ///
+ private readonly int column;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source 2D array instance.
+ /// The target column to iterate within .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Array2DColumnEnumerable(T[,] array, int column)
+ {
+ this.array = array;
+ this.column = column;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current 2D array instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.array, this.column);
+
+ ///
+ /// Returns a array with the values in the target column.
+ ///
+ /// A array with the values in the target column.
+ ///
+ /// This method will allocate a new array, so only
+ /// use it if you really need to copy the target items in a new memory location.
+ ///
+ [Pure]
+ public T[] ToArray()
+ {
+ if ((uint)column >= (uint)this.array.GetLength(1))
+ {
+ ThrowArgumentOutOfRangeExceptionForInvalidColumn();
+ }
+
+ int height = this.array.GetLength(0);
+
+ T[] array = new T[height];
+
+ for (int i = 0; i < height; i++)
+ {
+ array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(i, this.column);
+ }
+
+ return array;
+ }
+
+ ///
+ /// An enumerator for a source 2D array instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source 2D array instance.
+ ///
+ private readonly T[,] array;
+
+ ///
+ /// The target column to iterate within .
+ ///
+ private readonly int column;
+
+ ///
+ /// The height of a column in .
+ ///
+ private readonly int height;
+
+ ///
+ /// The current row.
+ ///
+ private int row;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source 2D array instance.
+ /// The target column to iterate within .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator(T[,] array, int column)
+ {
+ if ((uint)column >= (uint)array.GetLength(1))
+ {
+ ThrowArgumentOutOfRangeExceptionForInvalidColumn();
+ }
+
+ this.array = array;
+ this.column = column;
+ this.height = array.GetLength(0);
+ this.row = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int row = this.row + 1;
+
+ if (row < this.height)
+ {
+ this.row = row;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ public ref T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref this.array.DangerousGetReferenceAt(this.row, this.column);
+ }
+ }
+
+ ///
+ /// 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
new file mode 100644
index 00000000000..15c4aa335fd
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs
@@ -0,0 +1,167 @@
+// 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.
+
+#if NETSTANDARD2_0
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that iterates a row in a given 2D array instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct Array2DRowEnumerable
+ {
+ ///
+ /// The source 2D array instance.
+ ///
+ private readonly T[,] array;
+
+ ///
+ /// The target row to iterate within .
+ ///
+ private readonly int row;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source 2D array instance.
+ /// The target row to iterate within .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Array2DRowEnumerable(T[,] array, int row)
+ {
+ this.array = array;
+ this.row = row;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current 2D array instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.array, this.row);
+
+ ///
+ /// Returns a array with the values in the target row.
+ ///
+ /// A array with the values in the target row.
+ ///
+ /// This method will allocate a new array, so only
+ /// use it if you really need to copy the target items in a new memory location.
+ ///
+ [Pure]
+ public T[] ToArray()
+ {
+ if ((uint)row >= (uint)this.array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForInvalidRow();
+ }
+
+ int width = this.array.GetLength(1);
+
+ T[] array = new T[width];
+
+ for (int i = 0; i < width; i++)
+ {
+ array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(this.row, i);
+ }
+
+ return array;
+ }
+
+ ///
+ /// An enumerator for a source 2D array instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source 2D array instance.
+ ///
+ private readonly T[,] array;
+
+ ///
+ /// The target row to iterate within .
+ ///
+ private readonly int row;
+
+ ///
+ /// The width of a row in .
+ ///
+ private readonly int width;
+
+ ///
+ /// The current column.
+ ///
+ private int column;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source 2D array instance.
+ /// The target row to iterate within .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator(T[,] array, int row)
+ {
+ if ((uint)row >= (uint)array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForInvalidRow();
+ }
+
+ this.array = array;
+ this.row = row;
+ this.width = array.GetLength(1);
+ this.column = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int column = this.column + 1;
+
+ if (column < this.width)
+ {
+ this.column = column;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ public ref T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref this.array.DangerousGetReferenceAt(this.row, this.column);
+ }
+ }
+
+ ///
+ /// 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");
+ }
+ }
+}
+
+#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
new file mode 100644
index 00000000000..bd0b42c87df
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
@@ -0,0 +1,107 @@
+// 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.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that enumerates the items in a given instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct ReadOnlySpanEnumerable
+ {
+ ///
+ /// The source instance
+ ///
+ private readonly ReadOnlySpan span;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpanEnumerable(ReadOnlySpan span)
+ {
+ this.span = span;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.span);
+
+ ///
+ /// An enumerator for a source instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source instance.
+ ///
+ private readonly ReadOnlySpan span;
+
+ ///
+ /// The current index within .
+ ///
+ private int index;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator(ReadOnlySpan span)
+ {
+ this.span = span;
+ this.index = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int newIndex = this.index + 1;
+
+ if (newIndex < this.span.Length)
+ {
+ this.index = newIndex;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008", Justification = "ValueTuple return type")]
+ public (int Index, T Value) Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ int currentIndex = this.index;
+ T value = Unsafe.Add(ref MemoryMarshal.GetReference(this.span), currentIndex);
+
+ return (currentIndex, value);
+ }
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs
new file mode 100644
index 00000000000..8d4287c52b2
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs
@@ -0,0 +1,134 @@
+// 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.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that tokenizes a given instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct ReadOnlySpanTokenizer
+ where T : IEquatable
+ {
+ ///
+ /// The source instance
+ ///
+ private readonly ReadOnlySpan span;
+
+ ///
+ /// The separator item to use.
+ ///
+ private readonly T separator;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source to tokenize.
+ /// The separator item to use.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator)
+ {
+ this.span = span;
+ this.separator = separator;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.span, this.separator);
+
+ ///
+ /// An enumerator for a source instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source instance.
+ ///
+ private readonly ReadOnlySpan span;
+
+ ///
+ /// The separator item to use.
+ ///
+ private readonly T separator;
+
+ ///
+ /// The current initial offset.
+ ///
+ private int start;
+
+ ///
+ /// The current final offset.
+ ///
+ private int end;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source instance.
+ /// The separator item to use.
+ public Enumerator(ReadOnlySpan span, T separator)
+ {
+ this.span = span;
+ this.separator = separator;
+ this.start = 0;
+ this.end = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int
+ newEnd = this.end + 1,
+ length = this.span.Length;
+
+ // Additional check if the separator is not the last character
+ if (newEnd <= length)
+ {
+ this.start = newEnd;
+
+ int index = this.span.Slice(newEnd).IndexOf(this.separator);
+
+ // Extract the current subsequence
+ if (index >= 0)
+ {
+ this.end = newEnd + index;
+
+ return true;
+ }
+
+ this.end = length;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ public 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
new file mode 100644
index 00000000000..0c245b6b475
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs
@@ -0,0 +1,193 @@
+// 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.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that enumerates the items in a given instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct SpanEnumerable
+ {
+ ///
+ /// The source instance
+ ///
+ private readonly Span span;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SpanEnumerable(Span span)
+ {
+ this.span = span;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.span);
+
+ ///
+ /// An enumerator for a source instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source instance.
+ ///
+ private readonly Span span;
+
+ ///
+ /// The current index within .
+ ///
+ private int index;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator(Span span)
+ {
+ this.span = span;
+ this.index = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int newIndex = this.index + 1;
+
+ if (newIndex < this.span.Length)
+ {
+ this.index = newIndex;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ public Item Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if NETSTANDARD2_1
+ 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. */
+ return new Item(ref ri, this.index);
+#else
+ return new Item(this.span, this.index);
+#endif
+ }
+ }
+ }
+
+ ///
+ /// An item from a source instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct Item
+ {
+ ///
+ /// The source instance.
+ ///
+ private readonly Span span;
+
+#if NETSTANDARD2_1
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// A reference to the target value.
+ /// The index of the target value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Item(ref T value, int index)
+ {
+ this.span = MemoryMarshal.CreateSpan(ref value, index);
+ }
+#else
+ ///
+ /// The current index within .
+ ///
+ private readonly int index;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source instance.
+ /// The current index within .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Item(Span span, int index)
+ {
+ this.span = span;
+ this.index = index;
+ }
+#endif
+
+ ///
+ /// Gets the reference to the current value.
+ ///
+ public ref T Value
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if NETSTANDARD2_1
+ return ref MemoryMarshal.GetReference(this.span);
+#else
+ ref T r0 = ref MemoryMarshal.GetReference(this.span);
+ ref T ri = ref Unsafe.Add(ref r0, this.index);
+
+ return ref ri;
+#endif
+ }
+ }
+
+ ///
+ /// Gets the current index.
+ ///
+ public int Index
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if NETSTANDARD2_1
+ return this.span.Length;
+#else
+ return this.index;
+#endif
+ }
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs
new file mode 100644
index 00000000000..075b5c1c567
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs
@@ -0,0 +1,134 @@
+// 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.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that tokenizes a given instance.
+ ///
+ /// The type of items to enumerate.
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly ref struct SpanTokenizer
+ where T : IEquatable
+ {
+ ///
+ /// The source instance
+ ///
+ private readonly Span span;
+
+ ///
+ /// The separator item to use.
+ ///
+ private readonly T separator;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source to tokenize.
+ /// The separator item to use.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SpanTokenizer(Span span, T separator)
+ {
+ this.span = span;
+ this.separator = separator;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// An instance targeting the current value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator() => new Enumerator(this.span, this.separator);
+
+ ///
+ /// An enumerator for a source instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct Enumerator
+ {
+ ///
+ /// The source instance.
+ ///
+ private readonly Span span;
+
+ ///
+ /// The separator item to use.
+ ///
+ private readonly T separator;
+
+ ///
+ /// The current initial offset.
+ ///
+ private int start;
+
+ ///
+ /// The current final offset.
+ ///
+ private int end;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The source instance.
+ /// The separator item to use.
+ public Enumerator(Span span, T separator)
+ {
+ this.span = span;
+ this.separator = separator;
+ this.start = 0;
+ this.end = -1;
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ /// whether a new element is available, otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+ int
+ newEnd = this.end + 1,
+ length = this.span.Length;
+
+ // Additional check if the separator is not the last character
+ if (newEnd <= length)
+ {
+ this.start = newEnd;
+
+ int index = this.span.Slice(newEnd).IndexOf(this.separator);
+
+ // Extract the current subsequence
+ if (index >= 0)
+ {
+ this.end = newEnd + index;
+
+ return true;
+ }
+
+ this.end = length;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the duck-typed property.
+ ///
+ public Span Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.span.Slice(this.start, this.end - this.start);
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
new file mode 100644
index 00000000000..958a70546c3
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
@@ -0,0 +1,260 @@
+// 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.Diagnostics.Contracts;
+using System.Drawing;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Enumerables;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static partial class ArrayExtensions
+ {
+ ///
+ /// Returns a reference to the first element within a given 2D array, with no bounds checks.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input array instance.
+ /// A reference to the first element within , or the location it would have used, if is empty.
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReference(this T[,] array)
+ {
+ var arrayData = Unsafe.As(array);
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+
+ return ref r0;
+ }
+
+ ///
+ /// Returns a reference to an element at a specified coordinate within a given 2D array, with no bounds checks.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input 2D array instance.
+ /// The vertical index of the element to retrieve within .
+ /// The horizontal index of the element to retrieve within .
+ /// A reference to the element within at the coordinate specified by and .
+ ///
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the
+ /// and parameters are valid. Furthermore, this extension will ignore the lower bounds for the input
+ /// array, and will just assume that the input index is 0-based. It is responsability of the caller to adjust the input
+ /// indices to account for the actual lower bounds, if the input array has either axis not starting at 0.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j)
+ {
+ var arrayData = Unsafe.As(array);
+ int offset = (i * arrayData.Width) + j;
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+ ref T ri = ref Unsafe.Add(ref r0, offset);
+
+ return ref ri;
+ }
+
+ // Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
+ // CLR 2D arrays are laid out in memory as follows:
+ // [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ]
+ // ^ ^
+ // | \-- ref Unsafe.As(array).Data
+ // \-- array
+ // The length is always padded to IntPtr just like with SZ arrays.
+ // The total data padding is therefore 20 bytes on x86 (4 + 4 + 4 + 4 + 4), or 24 bytes on x64.
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class RawArray2DData
+ {
+#pragma warning disable CS0649 // Unassigned fields
+#pragma warning disable SA1401 // Fields should be private
+ public IntPtr Length;
+ public int Height;
+ public int Width;
+ public int HeightLowerBound;
+ public int WidthLowerBound;
+ public byte Data;
+#pragma warning restore CS0649
+#pragma warning restore SA1401
+ }
+
+ ///
+ /// Fills an area in a given 2D array instance with a specified value.
+ /// This API will try to fill as many items as possible, ignoring positions outside the bounds of the array.
+ /// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input array instance.
+ /// The value to fill the target area with.
+ /// The row to start on (inclusive, 0-based index).
+ /// The column to start on (inclusive, 0-based index).
+ /// The positive width of area to fill.
+ /// The positive height of area to fill.
+ public static void Fill(this T[,] array, T value, int row, int column, int width, int height)
+ {
+ Rectangle bounds = new Rectangle(0, 0, array.GetLength(1), array.GetLength(0));
+
+ // Precompute bounds to skip branching in main loop
+ bounds.Intersect(new Rectangle(column, row, width, height));
+
+ for (int i = bounds.Top; i < bounds.Bottom; i++)
+ {
+#if NETSTANDARD2_1
+ ref T r0 = ref array[i, bounds.Left];
+
+ // Span.Fill will use vectorized instructions when possible
+ MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
+#else
+ ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
+
+ for (int j = 0; j < bounds.Width; j++)
+ {
+ /* Storing the initial reference and only incrementing
+ * that one in each iteration saves one additional indirect
+ * dereference for every loop iteration compared to using
+ * the DangerousGetReferenceAt extension on the array. */
+ Unsafe.Add(ref r0, j) = value;
+ }
+#endif
+ }
+ }
+
+ ///
+ /// Returns a over a row in a given 2D array instance.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input array instance.
+ /// The target row to retrieve (0-based index).
+ /// A with the items from the target row within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static
+#if NETSTANDARD2_1
+ Span
+#else
+ /* .NET Standard 2.0 lacks MemoryMarshal.CreateSpan(ref T, int),
+ * which is necessary to create arbitrary Span-s over a 2D array.
+ * To work around this, we use a custom ref struct enumerator,
+ * which makes the lack of that API completely transparent to the user.
+ * If a user then moves from .NET Standard 2.0 to 2.1, all the previous
+ * features will be perfectly supported, and in addition to that it will
+ * also gain the ability to use the Span value elsewhere.
+ * The only case where this would be a breaking change for a user upgrading
+ * the target framework is when the returned enumerator type is used directly,
+ * but since that's specifically discouraged from the docs, we don't
+ * need to worry about that scenario in particular, as users doing that
+ * would be willingly go against the recommended usage of this API. */
+ Array2DRowEnumerable
+#endif
+ GetRow(this T[,] array, int row)
+ {
+ if ((uint)row >= (uint)array.GetLength(0))
+ {
+ throw new ArgumentOutOfRangeException(nameof(row));
+ }
+
+#if NETSTANDARD2_1
+ ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
+
+ return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
+#else
+ return new Array2DRowEnumerable(array, row);
+#endif
+ }
+
+ ///
+ /// Returns an enumerable that returns the items from a given column in a given 2D array instance.
+ /// This extension should be used directly within a loop:
+ ///
+ /// int[,] matrix =
+ /// {
+ /// { 1, 2, 3 },
+ /// { 4, 5, 6 },
+ /// { 7, 8, 9 }
+ /// };
+ ///
+ /// foreach (ref int number in matrix.GetColumn(1))
+ /// {
+ /// // Access the current number by reference here...
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input array instance.
+ /// The target column to retrieve (0-based index).
+ /// A wrapper type that will handle the column enumeration for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Array2DColumnEnumerable GetColumn(this T[,] array, int column)
+ {
+ return new Array2DColumnEnumerable(array, column);
+ }
+
+#if NETSTANDARD2_1
+ ///
+ /// Cretes a new over an input 2D array.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input 2D array instance.
+ /// A instance with the values of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(this T[,] array)
+ {
+ var arrayData = Unsafe.As(array);
+
+ /* On x64, the length is padded to x64, but it is represented in memory
+ * as two sequential uint fields (one of which is padding).
+ * So we can just reinterpret a reference to the IntPtr as one of type
+ * uint, to access the first 4 bytes of that field, regardless of whether
+ * we're running in a 32 or 64 bit process. This will work when on little
+ * endian systems as well, as the memory layout for fields is the same,
+ * the only difference is the order of bytes within each field of a given type.
+ * We use checked here to follow suit with the CoreCLR source, where an
+ * invalid value here should fail to perform the cast and throw an exception. */
+ int length = checked((int)Unsafe.As(ref arrayData.Length));
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+
+ return MemoryMarshal.CreateSpan(ref r0, length);
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target 2D array instance.
+ ///
+ /// The type of items in the input 2D array instance.
+ /// The input 2D array instance.
+ /// The value to look for.
+ /// The number of occurrences of in .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Count(this T[,] array, T value)
+ where T : IEquatable
+ {
+ return ReadOnlySpanExtensions.Count(array.AsSpan(), value);
+ }
+
+ ///
+ /// Gets a content hash from the input 2D array instance using the Djb2 algorithm.
+ ///
+ /// The type of items in the input 2D array instance.
+ /// The input 2D array instance.
+ /// The Djb2 value for the input 2D array instance.
+ /// The Djb2 hash is fully deterministic and with no random components.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetDjb2HashCode(this T[,] array)
+ where T : notnull
+ {
+ return ReadOnlySpanExtensions.GetDjb2HashCode(array.AsSpan());
+ }
+#endif
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
new file mode 100644
index 00000000000..4af7e3fce53
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
@@ -0,0 +1,158 @@
+// 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.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Enumerables;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static partial class ArrayExtensions
+ {
+ ///
+ /// Returns a reference to the first element within a given array, with no bounds checks.
+ ///
+ /// The type of elements in the input array instance.
+ /// The input array instance.
+ /// A reference to the first element within , or the location it would have used, if is empty.
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReference(this T[] array)
+ {
+ var arrayData = Unsafe.As(array);
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+
+ return ref r0;
+ }
+
+ ///
+ /// Returns a reference to an element at a specified index within a given array, with no bounds checks.
+ ///
+ /// The type of elements in the input array instance.
+ /// The input array instance.
+ /// The index of the element to retrieve within .
+ /// A reference to the element within at the index specified by .
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReferenceAt(this T[] array, int i)
+ {
+ var arrayData = Unsafe.As(array);
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ return ref ri;
+ }
+
+ // Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
+ // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
+ // [ sync block || pMethodTable || num components || MD array bounds || array data .. ]
+ // ^ ^ ^ returned reference
+ // | \-- ref Unsafe.As(array).Data
+ // \-- array
+ // The base size of an array includes all the fields before the array data,
+ // including the sync block and method table. The reference to RawData.Data
+ // points at the number of components, skipping over these two pointer-sized fields.
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class RawArrayData
+ {
+#pragma warning disable CS0649 // Unassigned fields
+#pragma warning disable SA1401 // Fields should be private
+ public IntPtr Length;
+ public byte Data;
+#pragma warning restore CS0649
+#pragma warning restore SA1401
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target array instance.
+ ///
+ /// The type of items in the input array instance.
+ /// The input array instance.
+ /// The value to look for.
+ /// The number of occurrences of in .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Count(this T[] array, T value)
+ where T : IEquatable
+ {
+ return ReadOnlySpanExtensions.Count(array, value);
+ }
+
+ ///
+ /// Enumerates the items in the input array instance, as pairs of reference/index values.
+ /// This extension should be used directly within a loop:
+ ///
+ /// int[] numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
+ ///
+ /// foreach (var item in numbers.Enumerate())
+ /// {
+ /// // Access the index and value of each item here...
+ /// int index = item.Index;
+ /// ref int value = ref item.Value;
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items to enumerate.
+ /// The source array to enumerate.
+ /// A wrapper type that will handle the reference/index enumeration for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanEnumerable Enumerate(this T[] array)
+ {
+ return new SpanEnumerable(array);
+ }
+
+ ///
+ /// Tokenizes the values in the input array instance using a specified separator.
+ /// This extension should be used directly within a loop:
+ ///
+ /// char[] text = "Hello, world!".ToCharArray();
+ ///
+ /// foreach (var token in text.Tokenize(','))
+ /// {
+ /// // Access the tokens here...
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items in the array to tokenize.
+ /// The source array to tokenize.
+ /// The separator item to use.
+ /// A wrapper type that will handle the tokenization for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanTokenizer Tokenize(this T[] array, T separator)
+ where T : IEquatable
+ {
+ return new SpanTokenizer(array, separator);
+ }
+
+ ///
+ /// Gets a content hash from the input array instance using the Djb2 algorithm.
+ ///
+ /// The type of items in the input array instance.
+ /// The input array instance.
+ /// The Djb2 value for the input array instance.
+ /// The Djb2 hash is fully deterministic and with no random components.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetDjb2HashCode(this T[] array)
+ where T : notnull
+ {
+ return ReadOnlySpanExtensions.GetDjb2HashCode(array);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs
new file mode 100644
index 00000000000..b5ef49b007a
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs
@@ -0,0 +1,58 @@
+// 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;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class ArrayPoolExtensions
+ {
+ ///
+ /// Changes the number of elements of a rented one-dimensional array to the specified new size.
+ ///
+ /// The type of items into the target array to resize.
+ /// The target instance to use to resize the array.
+ /// The rented array to resize, or to create a new array.
+ /// The size of the new array.
+ /// Indicates whether the contents of the array should be cleared before reuse.
+ /// Thrown when is less than 0.
+ /// When this method returns, the caller must not use any references to the old array anymore.
+ public static void Resize(this ArrayPool pool, ref T[]? array, int newSize, bool clearArray = false)
+ {
+ // If the old array is null, just create a new one with the requested size
+ if (array is null)
+ {
+ array = pool.Rent(newSize);
+
+ return;
+ }
+
+ // If the new size is the same as the current size, do nothing
+ if (array.Length == newSize)
+ {
+ return;
+ }
+
+ /* Rent a new array with the specified size, and copy as many items from the current array
+ * as possible to the new array. This mirrors the behavior of the Array.Resize API from
+ * the BCL: if the new size is greater than the length of the current array, copy all the
+ * items from the original array into the new one. Otherwise, copy as many items as possible,
+ * until the new array is completely filled, and ignore the remaining items in the first array. */
+ T[] newArray = pool.Rent(newSize);
+ int itemsToCopy = Math.Min(array.Length, newSize);
+
+ array.AsSpan(0, itemsToCopy).CopyTo(newArray);
+
+ pool.Return(array, clearArray);
+
+ array = newArray;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs
new file mode 100644
index 00000000000..89fc34499b0
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs
@@ -0,0 +1,28 @@
+// 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.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class BoolExtensions
+ {
+ ///
+ /// Converts the given value into an .
+ ///
+ /// The input value to convert.
+ /// 1 if is , 0 otherwise.
+ /// This method does not contain branching instructions.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ToInt(this bool flag)
+ {
+ return Unsafe.As(ref flag);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs
new file mode 100644
index 00000000000..33b26d509c7
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs
@@ -0,0 +1,38 @@
+// 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 Microsoft.Toolkit.HighPerformance.Helpers;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class HashCodeExtensions
+ {
+ ///
+ /// Adds a sequence of values to the hash code.
+ ///
+ /// The type of elements in the input instance.
+ /// The input instance.
+ /// The input instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Add(ref this HashCode hashCode, ReadOnlySpan span)
+#if NETSTANDARD2_1
+ where T : notnull
+#else
+ // Same type constraints as HashCode, see comments there
+ where T : unmanaged
+#endif
+ {
+ int hash = HashCode.CombineValues(span);
+
+ hashCode.Add(hash);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs
new file mode 100644
index 00000000000..a984e72aedb
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/IMemoryOwnerExtensions.cs
@@ -0,0 +1,36 @@
+// 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.Diagnostics.Contracts;
+using System.IO;
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Streams;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class IMemoryOwnerExtensions
+ {
+ ///
+ /// Returns a wrapping the contents of the given of instance.
+ ///
+ /// 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)
+ {
+ return new IMemoryOwnerStream(memory);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
new file mode 100644
index 00000000000..2781507dc81
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
@@ -0,0 +1,38 @@
+// 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.Diagnostics.Contracts;
+using System.IO;
+using System.Runtime.CompilerServices;
+using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class MemoryExtensions
+ {
+ ///
+ /// Returns a wrapping the contents of the given of instance.
+ ///
+ /// The input of instance.
+ /// A wrapping the data within .
+ ///
+ /// Since this method only receives a instance, which does not track
+ /// the lifetime of its underlying buffer, it is responsability of the caller to manage that.
+ /// 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.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Stream AsStream(this Memory memory)
+ {
+ return new MemoryStream(memory);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ObjectExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ObjectExtensions.cs
new file mode 100644
index 00000000000..2bb1c8d0014
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ObjectExtensions.cs
@@ -0,0 +1,78 @@
+// 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.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with instances.
+ ///
+ public static class ObjectExtensions
+ {
+ ///
+ /// Tries to get a boxed value from an input instance.
+ ///
+ /// The type of value to try to unbox.
+ /// The input instance to check.
+ /// The resulting value, if was in fact a boxed value.
+ /// if a value was retrieved correctly, otherwise.
+ ///
+ /// This extension behaves just like the following method:
+ ///
+ /// public static bool TryUnbox<T>(this object obj, out T value)
+ /// {
+ /// if (obj is T)
+ /// {
+ /// value = (T)obj;
+ ///
+ /// return true;
+ /// }
+ ///
+ /// value = default;
+ ///
+ /// return false;
+ /// }
+ ///
+ /// But in a more efficient way, and with the ability to also assign the unboxed value
+ /// directly on an existing T variable, which is not possible with the code above.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryUnbox(this object obj, out T value)
+ where T : struct
+ {
+ if (obj.GetType() == typeof(T))
+ {
+ value = Unsafe.Unbox(obj);
+
+ return true;
+ }
+
+ value = default;
+
+ return false;
+ }
+
+ ///
+ /// Unboxes a value from an input instance.
+ ///
+ /// The type of value to unbox.
+ /// The input instance, representing a boxed value.
+ /// The value boxed in .
+ ///
+ /// This method doesn't check the actual type of , so it is responsability of the caller
+ /// to ensure it actually represents a boxed value and not some other instance.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousUnbox(this object obj)
+ where T : struct
+ {
+ return ref Unsafe.Unbox(obj);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
new file mode 100644
index 00000000000..bf5a50c9656
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
@@ -0,0 +1,38 @@
+// 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.Diagnostics.Contracts;
+using System.IO;
+using System.Runtime.CompilerServices;
+using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class ReadOnlyMemoryExtensions
+ {
+ ///
+ /// Returns a wrapping the contents of the given of instance.
+ ///
+ /// The input of instance.
+ /// A wrapping the data within .
+ ///
+ /// Since this method only receives a instance, which does not track
+ /// the lifetime of its underlying buffer, it is responsability of the caller to manage that.
+ /// 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.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Stream AsStream(this ReadOnlyMemory memory)
+ {
+ return new MemoryStream(memory);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.Count.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.Count.cs
new file mode 100644
index 00000000000..d470e456457
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.Count.cs
@@ -0,0 +1,252 @@
+// 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.Diagnostics.Contracts;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static partial class ReadOnlySpanExtensions
+ {
+ ///
+ /// Counts the number of occurrences of a given value into a target instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance to read.
+ /// The value to look for.
+ /// The number of occurrences of in .
+ [Pure]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static int Count(this ReadOnlySpan span, T value)
+ where T : IEquatable
+ {
+ // Special vectorized version when using a supported type
+ if (typeof(T) == typeof(byte) ||
+ typeof(T) == typeof(sbyte) ||
+ typeof(T) == typeof(bool))
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref sbyte r1 = ref Unsafe.As(ref r0);
+ int length = span.Length;
+ sbyte target = Unsafe.As(ref value);
+
+ return Count(ref r1, length, target, sbyte.MaxValue);
+ }
+
+ if (typeof(T) == typeof(char) ||
+ typeof(T) == typeof(ushort) ||
+ typeof(T) == typeof(short))
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref short r1 = ref Unsafe.As(ref r0);
+ int length = span.Length;
+ short target = Unsafe.As(ref value);
+
+ return Count(ref r1, length, target, short.MaxValue);
+ }
+
+ if (typeof(T) == typeof(int) ||
+ typeof(T) == typeof(uint))
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref int r1 = ref Unsafe.As(ref r0);
+ int length = span.Length;
+ int target = Unsafe.As(ref value);
+
+ return Count(ref r1, length, target, int.MaxValue);
+ }
+
+ if (typeof(T) == typeof(long) ||
+ typeof(T) == typeof(ulong))
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref long r1 = ref Unsafe.As(ref r0);
+ int length = span.Length;
+ long target = Unsafe.As(ref value);
+
+ return Count(ref r1, length, target, int.MaxValue);
+ }
+
+ return Count(ref MemoryMarshal.GetReference(span), span.Length, value);
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target search space.
+ ///
+ /// A reference to the start of the search space.
+ /// The number of items in the search space.
+ /// The value to look for.
+ /// The type of value to look for.
+ /// The number of occurrences of in the search space
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Count(ref T r0, int length, T value)
+ where T : IEquatable
+ {
+ int
+ i = 0,
+ result = 0,
+ end8 = length - 8;
+
+ // Main loop with 8 unrolled iterations
+ for (; i <= end8; i += 8)
+ {
+ result += Unsafe.Add(ref r0, i + 0).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 1).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 2).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 3).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 4).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 5).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 6).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, i + 7).Equals(value).ToInt();
+ }
+
+ // Iterate over the remaining values and count those that match
+ for (; i < length; i++)
+ {
+ result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
+ }
+
+ return result;
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target search space.
+ ///
+ /// A reference to the start of the search space.
+ /// The number of items in the search space.
+ /// The value to look for.
+ /// The maximum amount that a value can reach.
+ /// The type of value to look for.
+ /// The number of occurrences of in the search space
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Count(ref T r0, int length, T value, int max)
+ where T : unmanaged, IEquatable
+ {
+ int i = 0, result = 0;
+
+ // Only run the SIMD-enabled branch if the Vector APIs are hardware accelerated
+ if (Vector.IsHardwareAccelerated)
+ {
+ int end = length - Vector.Count;
+
+ var partials = Vector.Zero;
+ var vc = new Vector(value);
+
+ /* Run the fast path if the input span is short enough.
+ * There are two branches here because if the search space is large
+ * enough, the partial results could overflow if the target value
+ * is present too many times. This upper limit depends on the type
+ * being used, as the smaller the type is, the shorter the supported
+ * fast range will be. In the worst case scenario, the same value appears
+ * always at the offset aligned with the same SIMD value in the current
+ * register. Therefore, if the input span is longer than that minimum
+ * threshold, additional checks need to be performed to avoid overflows.
+ * This value is equal to the maximum (signed) numerical value for the current
+ * type, divided by the number of value that can fit in a register, minus 1.
+ * This is because the partial results are accumulated with a dot product,
+ * which sums them horizontally while still working on the original type.
+ * Dividing the max value by their count ensures that overflows can't happen.
+ * The check is moved outside of the loop to enable a branchless version
+ * of this method if the input span is guaranteed not to cause overflows.
+ * Otherwise, the safe but slower variant is used. */
+ int threshold = (max / Vector.Count) - 1;
+
+ if (length <= threshold)
+ {
+ for (; i <= end; i += Vector.Count)
+ {
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ /* Load the current Vector register, and then use
+ * Vector.Equals to check for matches. This API sets the
+ * values corresponding to matching pairs to all 1s.
+ * Since the input type is guaranteed to always be signed,
+ * this means that a value with all 1s represents -1, as
+ * signed numbers are represented in two's complement.
+ * So we can subtract this intermediate value to the
+ * partial results, which effectively sums 1 for each match. */
+ var vi = Unsafe.As>(ref ri);
+ var ve = Vector.Equals(vi, vc);
+
+ partials -= ve;
+ }
+ }
+ else
+ {
+ for (int j = 0; i <= end; i += Vector.Count, j++)
+ {
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ // Same as before
+ var vi = Unsafe.As>(ref ri);
+ var ve = Vector.Equals(vi, vc);
+
+ partials -= ve;
+
+ // Additional checks to avoid overflows
+ if (j == threshold)
+ {
+ j = 0;
+ result += CastToInt(Vector.Dot(partials, Vector.One));
+ partials = Vector.Zero;
+ }
+ }
+ }
+
+ // Compute the horizontal sum of the partial results
+ result += CastToInt(Vector.Dot(partials, Vector.One));
+ }
+
+ // Iterate over the remaining values and count those that match
+ for (; i < length; i++)
+ {
+ result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
+ }
+
+ return result;
+ }
+
+ ///
+ /// Casts a value of a given type to .
+ ///
+ /// The input type to cast.
+ /// The input value to cast to .
+ /// The cast of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int CastToInt(T value)
+ where T : unmanaged
+ {
+ if (typeof(T) == typeof(sbyte))
+ {
+ return Unsafe.As(ref value);
+ }
+
+ if (typeof(T) == typeof(short))
+ {
+ return Unsafe.As(ref value);
+ }
+
+ if (typeof(T) == typeof(int))
+ {
+ return Unsafe.As(ref value);
+ }
+
+ if (typeof(T) == typeof(long))
+ {
+ return (int)Unsafe.As(ref value);
+ }
+
+ throw new ArgumentException($"Invalid input type {typeof(T)}", nameof(value));
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs
new file mode 100644
index 00000000000..c3fffe8cfa5
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs
@@ -0,0 +1,188 @@
+// 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.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Enumerables;
+
+#nullable enable
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static partial class ReadOnlySpanExtensions
+ {
+ ///
+ /// Returns a reference to the first element within a given , with no bounds checks.
+ ///
+ /// The type of elements in the input instance.
+ /// The input instance.
+ /// A reference to the first element within .
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReference(this ReadOnlySpan span)
+ {
+ return ref MemoryMarshal.GetReference(span);
+ }
+
+ ///
+ /// Returns a reference to an element at a specified index within a given , with no bounds checks.
+ ///
+ /// The type of elements in the input instance.
+ /// The input instance.
+ /// The index of the element to retrieve within .
+ /// A reference to the element within at the index specified by .
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i)
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ return ref ri;
+ }
+
+ ///
+ /// Casts a of one primitive type to of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ ///
+ /// The type if items in the source
+ /// The source slice, of type .
+ /// A of bytes.
+ ///
+ /// Thrown when contains pointers.
+ ///
+ ///
+ /// Thrown if the property of the new would exceed .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsBytes(this ReadOnlySpan span)
+ where T : unmanaged
+ {
+ return MemoryMarshal.AsBytes(span);
+ }
+
+ ///
+ /// Casts a of one primitive type to another primitive type .
+ /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ ///
+ /// The type of items in the source .
+ /// The type of items in the destination .
+ /// The source slice, of type .
+ /// A of type
+ ///
+ /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
+ ///
+ ///
+ /// Thrown when or contains pointers.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan Cast(this ReadOnlySpan span)
+ where TFrom : struct
+ where TTo : struct
+ {
+ return MemoryMarshal.Cast(span);
+ }
+
+ ///
+ /// Enumerates the items in the input instance, as pairs of value/index values.
+ /// This extension should be used directly within a loop:
+ ///
+ /// ReadOnlySpan<string> words = new[] { "Hello", ", ", "world", "!" };
+ ///
+ /// foreach (var item in words.Enumerate())
+ /// {
+ /// // Access the index and value of each item here...
+ /// int index = item.Index;
+ /// string value = item.Value;
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items to enumerate.
+ /// The source to enumerate.
+ /// A wrapper type that will handle the value/index enumeration for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpanEnumerable Enumerate(this ReadOnlySpan span)
+ {
+ return new ReadOnlySpanEnumerable(span);
+ }
+
+ ///
+ /// Tokenizes the values in the input instance using a specified separator.
+ /// This extension should be used directly within a loop:
+ ///
+ /// ReadOnlySpan<char> text = "Hello, world!";
+ ///
+ /// foreach (var token in text.Tokenize(','))
+ /// {
+ /// // Access the tokens here...
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items in the to tokenize.
+ /// The source to tokenize.
+ /// The separator item to use.
+ /// A wrapper type that will handle the tokenization for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpanTokenizer Tokenize(this ReadOnlySpan span, T separator)
+ where T : IEquatable
+ {
+ return new ReadOnlySpanTokenizer(span, separator);
+ }
+
+ ///
+ /// Gets a content hash from the input instance using the Djb2 algorithm.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The Djb2 value for the input instance.
+ /// The Djb2 hash is fully deterministic and with no random components.
+ [Pure]
+ public static int GetDjb2HashCode(this ReadOnlySpan span)
+ where T : notnull
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ int
+ hash = 5381,
+ length = span.Length,
+ i = 0,
+ end8 = length - 8;
+
+ // Main loop with 8 unrolled iterations
+ for (; i <= end8; i += 8)
+ {
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 0).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 1).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 2).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 3).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 4).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 5).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 6).GetHashCode());
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 7).GetHashCode());
+ }
+
+ // Handle the leftover items
+ for (; i < length; i++)
+ {
+ hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i).GetHashCode());
+ }
+
+ return hash;
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
new file mode 100644
index 00000000000..5710c08e783
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
@@ -0,0 +1,176 @@
+// 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.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Enumerables;
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///
+ /// Helpers for working with the type.
+ ///
+ public static class SpanExtensions
+ {
+ ///
+ /// Returns a reference to the first element within a given , with no bounds checks.
+ ///
+ /// The type of elements in the input instance.
+ /// The input instance.
+ /// A reference to the first element within .
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReference(this Span span)
+ {
+ return ref MemoryMarshal.GetReference(span);
+ }
+
+ ///
+ /// Returns a reference to an element at a specified index within a given , with no bounds checks.
+ ///
+ /// The type of elements in the input instance.
+ /// The input instance.
+ /// The index of the element to retrieve within .
+ /// A reference to the element within at the index specified by .
+ /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T DangerousGetReferenceAt(this Span span, int i)
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ return ref ri;
+ }
+
+ ///
+ /// Casts a of one primitive type to of bytes.
+ /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ ///
+ /// The type if items in the source
+ /// The source slice, of type .
+ /// A of bytes.
+ ///
+ /// Thrown when contains pointers.
+ ///
+ ///
+ /// Thrown if the property of the new would exceed .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsBytes(this Span span)
+ where T : unmanaged
+ {
+ return MemoryMarshal.AsBytes(span);
+ }
+
+ ///
+ /// Casts a of one primitive type to another primitive type .
+ /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
+ ///
+ /// The type of items in the source .
+ /// The type of items in the destination .
+ /// The source slice, of type .
+ /// A of type
+ ///
+ /// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
+ ///
+ ///
+ /// Thrown when or contains pointers.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span Cast(this Span span)
+ where TFrom : struct
+ where TTo : struct
+ {
+ return MemoryMarshal.Cast(span);
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance to read.
+ /// The value to look for.
+ /// The number of occurrences of in .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Count(this Span span, T value)
+ where T : IEquatable
+ {
+ return ReadOnlySpanExtensions.Count(span, value);
+ }
+
+ ///
+ /// Enumerates the items in the input instance, as pairs of reference/index values.
+ /// This extension should be used directly within a loop:
+ ///
+ /// Span<int> numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
+ ///
+ /// foreach (var item in numbers.Enumerate())
+ /// {
+ /// // Access the index and value of each item here...
+ /// int index = item.Index;
+ /// ref int value = ref item.Value;
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items to enumerate.
+ /// The source to enumerate.
+ /// A wrapper type that will handle the reference/index enumeration for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanEnumerable Enumerate(this Span span)
+ {
+ return new SpanEnumerable(span);
+ }
+
+ ///
+ /// Tokenizes the values in the input instance using a specified separator.
+ /// This extension should be used directly within a loop:
+ ///
+ /// Span<char> text = "Hello, world!".ToCharArray();
+ ///
+ /// foreach (var token in text.Tokenize(','))
+ /// {
+ /// // Access the tokens here...
+ /// }
+ ///
+ /// The compiler will take care of properly setting up the loop with the type returned from this method.
+ ///
+ /// The type of items in the to tokenize.
+ /// The source to tokenize.
+ /// The separator item to use.
+ /// A wrapper type that will handle the tokenization for .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanTokenizer Tokenize(this Span span, T separator)
+ where T : IEquatable
+ {
+ return new SpanTokenizer(span, separator);
+ }
+
+ ///
+ /// Gets a content hash from the input instance using the Djb2 algorithm.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The Djb2 value for the input instance.
+ /// The Djb2 hash is fully deterministic and with no random components.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetDjb2HashCode(this Span span)
+ where T : notnull
+ {
+ return ReadOnlySpanExtensions.GetDjb2HashCode(span);
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpinLockExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpinLockExtensions.cs
new file mode 100644
index 00000000000..e2d52b61186
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpinLockExtensions.cs
@@ -0,0 +1,191 @@
+// 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.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Microsoft.Toolkit.HighPerformance.Extensions
+{
+ ///