diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs
index e17c0c48a18..9e1c032b1f2 100644
--- a/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/ArrayPoolBufferWriter{T}.cs
@@ -24,7 +24,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// the arrays in use are rented from the shared instance,
/// and that is also available on .NET Standard 2.0.
///
- [DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class ArrayPoolBufferWriter : IBuffer, IMemoryOwner
{
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs
new file mode 100644
index 00000000000..89ce81ea208
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Internals/RawObjectMemoryManager{T}.cs
@@ -0,0 +1,98 @@
+// 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 SPAN_RUNTIME_SUPPORT
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
+{
+ ///
+ /// A custom that can wrap arbitrary instances.
+ ///
+ /// The type of elements in the target memory area.
+ internal sealed class RawObjectMemoryManager : MemoryManager
+ {
+ ///
+ /// The target instance.
+ ///
+ private readonly object instance;
+
+ ///
+ /// The initial offset within .
+ ///
+ private readonly IntPtr offset;
+
+ ///
+ /// The length of the target memory area.
+ ///
+ private readonly int length;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target instance.
+ /// The starting offset within .
+ /// The usable length within .
+ public RawObjectMemoryManager(object instance, IntPtr offset, int length)
+ {
+ this.instance = instance;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ ///
+ public override Span GetSpan()
+ {
+ ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset);
+
+ return MemoryMarshal.CreateSpan(ref r0, this.length);
+ }
+
+ ///
+ public override unsafe MemoryHandle Pin(int elementIndex = 0)
+ {
+ if ((uint)elementIndex >= (uint)this.length)
+ {
+ ThrowArgumentOutOfRangeExceptionForInvalidElementIndex();
+ }
+
+ // Allocating a pinned handle for the array with fail and throw an exception
+ // if the array contains non blittable data. This is the expected behavior and
+ // the same happens when trying to pin a Memory instance obtained through
+ // traditional means (eg. via the implicit T[] array conversion), if T is a
+ // reference type or a type containing some references.
+ GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
+ ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt(this.offset);
+ ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex);
+ void* p = Unsafe.AsPointer(ref r1);
+
+ return new MemoryHandle(p, handle);
+ }
+
+ ///
+ public override void Unpin()
+ {
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ ///
+ /// Throws an when the input index for is not valid.
+ ///
+ private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex()
+ {
+ throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range");
+ }
+ }
+}
+
+#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs
index 9203eebc182..2dca3d29c03 100644
--- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs
@@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Views;
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
@@ -20,7 +21,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// instances (or objects that can be converted to a ), to ensure the data is written directly
/// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity.
///
- [DebuggerTypeProxy(typeof(MemoryBufferWriter<>))]
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryBufferWriter : IBuffer
{
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs
index b243a5fdb5d..7f6391a967a 100644
--- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryOwner{T}.cs
@@ -16,7 +16,7 @@ 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<>))]
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryOwner : IMemoryOwner
{
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs
index 481f60f8195..6db21491cc5 100644
--- a/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/SpanOwner{T}.cs
@@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// 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<>))]
+ [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly ref struct SpanOwner
{
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs b/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs
index d2a98ae8c94..b2f75bbf0d3 100644
--- a/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/StringPool.cs
@@ -536,7 +536,7 @@ private unsafe ref string TryGet(ReadOnlySpan span, int hashcode)
(uint)i < (uint)length;
i = entry.NextIndex)
{
- entry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)i);
+ entry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)i);
if (entry.HashCode == hashcode &&
entry.Value!.AsSpan().SequenceEqual(span))
@@ -556,7 +556,7 @@ private unsafe ref string TryGet(ReadOnlySpan span, int hashcode)
/// The new instance to store.
/// The precomputed hashcode for .
[MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe void Insert(string value, int hashcode)
+ private void Insert(string value, int hashcode)
{
ref int bucketsRef = ref this.buckets.DangerousGetReference();
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
@@ -571,7 +571,7 @@ private unsafe void Insert(string value, int hashcode)
entryIndex = heapEntriesRef.MapIndex;
heapIndex = 0;
- ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
+ ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
// The removal logic can be extremely optimized in this case, as we
// can retrieve the precomputed hashcode for the target entry by doing
@@ -588,9 +588,9 @@ private unsafe void Insert(string value, int hashcode)
}
int bucketIndex = hashcode & (this.buckets.Length - 1);
- ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (IntPtr)(void*)(uint)bucketIndex);
- ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
- ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)heapIndex);
+ ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (nint)(uint)bucketIndex);
+ ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
+ ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)heapIndex);
// Assign the values in the new map entry
targetMapEntry.HashCode = hashcode;
@@ -616,7 +616,7 @@ private unsafe void Insert(string value, int hashcode)
/// The index of the target map node to remove.
/// The input instance needs to already exist in the map.
[MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe void Remove(int hashcode, int mapIndex)
+ private void Remove(int hashcode, int mapIndex)
{
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
int
@@ -628,7 +628,7 @@ private unsafe void Remove(int hashcode, int mapIndex)
// value we're looking for is guaranteed to be present
while (true)
{
- ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
+ ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
// Check the current value for a match
if (entryIndex == mapIndex)
@@ -636,7 +636,7 @@ private unsafe void Remove(int hashcode, int mapIndex)
// If this was not the first list node, update the parent as well
if (lastIndex != EndOfList)
{
- ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)lastIndex);
+ ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)lastIndex);
lastEntry.NextIndex = candidate.NextIndex;
}
@@ -662,14 +662,14 @@ private unsafe void Remove(int hashcode, int mapIndex)
///
/// The index of the target heap node to update.
[MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe void UpdateTimestamp(ref int heapIndex)
+ private void UpdateTimestamp(ref int heapIndex)
{
int
currentIndex = heapIndex,
count = this.count;
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
- ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
+ ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
uint timestamp = this.timestamp;
// Check if incrementing the current timestamp for the heap node to update
@@ -721,7 +721,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex)
// Check and update the left child, if necessary
if (left < count)
{
- ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)left);
+ ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)left);
if (child.Timestamp < minimum.Timestamp)
{
@@ -733,7 +733,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex)
// Same check as above for the right child
if (right < count)
{
- ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)right);
+ ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)right);
if (child.Timestamp < minimum.Timestamp)
{
@@ -752,8 +752,8 @@ private unsafe void UpdateTimestamp(ref int heapIndex)
}
// Update the indices in the respective map entries (accounting for the swap)
- Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)root.MapIndex).HeapIndex = targetIndex;
- Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)minimum.MapIndex).HeapIndex = currentIndex;
+ Unsafe.Add(ref mapEntriesRef, (nint)(uint)root.MapIndex).HeapIndex = targetIndex;
+ Unsafe.Add(ref mapEntriesRef, (nint)(uint)minimum.MapIndex).HeapIndex = currentIndex;
currentIndex = targetIndex;
@@ -764,7 +764,7 @@ private unsafe void UpdateTimestamp(ref int heapIndex)
minimum = temp;
// Update the reference to the root node
- root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
+ root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
}
Fallback:
@@ -787,14 +787,14 @@ private unsafe void UpdateTimestamp(ref int heapIndex)
/// a given number of nodes, those are all contiguous from the start of the array.
///
[MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe void UpdateAllTimestamps()
+ private void UpdateAllTimestamps()
{
int count = this.count;
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
for (int i = 0; i < count; i++)
{
- Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)i).Timestamp = (uint)i;
+ Unsafe.Add(ref heapEntriesRef, (nint)(uint)i).Timestamp = (uint)i;
}
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs
deleted file mode 100644
index 216ec3d5939..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/ArrayPoolBufferWriterDebugView{T}.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-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/MemoryBufferWriterDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs
deleted file mode 100644
index b034647d86d..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryBufferWriterDebugView{T}.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-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 MemoryBufferWriterDebugView
- {
- ///
- /// Initializes a new instance of the class with the specified parameters.
- ///
- /// The input instance with the items to display.
- public MemoryBufferWriterDebugView(MemoryBufferWriter? memoryBufferWriter)
- {
- this.Items = memoryBufferWriter?.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/MemoryDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs
new file mode 100644
index 00000000000..8dcfd81a19d
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryDebugView{T}.cs
@@ -0,0 +1,57 @@
+// 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;
+
+namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
+{
+ ///
+ /// A debug proxy used to display items in a 1D layout.
+ ///
+ /// The type of items to display.
+ internal sealed class MemoryDebugView
+ {
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public MemoryDebugView(ArrayPoolBufferWriter? arrayPoolBufferWriter)
+ {
+ this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public MemoryDebugView(MemoryBufferWriter? memoryBufferWriter)
+ {
+ this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public MemoryDebugView(MemoryOwner? memoryOwner)
+ {
+ this.Items = memoryOwner?.Span.ToArray();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The input instance with the items to display.
+ public MemoryDebugView(SpanOwner spanOwner)
+ {
+ this.Items = spanOwner.Span.ToArray();
+ }
+
+ ///
+ /// Gets the items to display for the current instance
+ ///
+ [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
+ public T[]? Items { get; }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs
deleted file mode 100644
index 980115fa426..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/MemoryOwnerDebugView{T}.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-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
deleted file mode 100644
index 84802700b97..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Buffers/Views/SpanOwnerDebugView{T}.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-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
deleted file mode 100644
index 629d5f903f8..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DColumnEnumerable{T}.cs
+++ /dev/null
@@ -1,213 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-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.
- [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];
-
- ref T r0 = ref array.DangerousGetReference();
- int i = 0;
-
- // Leverage the enumerator to traverse the column
- foreach (T item in this)
- {
- Unsafe.Add(ref r0, i++) = item;
- }
-
- return array;
- }
-
- ///
- /// An enumerator for a source 2D array instance.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public ref struct Enumerator
- {
-#if SPAN_RUNTIME_SUPPORT
- ///
- /// The instance mapping the target 2D array.
- ///
- ///
- /// In runtimes where we have support for the type, we can
- /// create one from the input 2D array and use that to traverse the target column.
- /// This reduces the number of operations to perform for the offsetting to the right
- /// column element (we simply need to add to the offset at each
- /// iteration to move down by one row), and allows us to use the fast
- /// accessor instead of the slower indexer for 2D arrays, as we can then access each
- /// individual item linearly, since we know the absolute offset from the base location.
- ///
- private readonly Span span;
-
- ///
- /// The width of the target 2D array.
- ///
- private readonly int width;
-
- ///
- /// The current absolute offset within .
- ///
- private int offset;
-#else
- ///
- /// 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;
-#endif
-
- ///
- /// 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();
- }
-
-#if SPAN_RUNTIME_SUPPORT
- this.span = array.AsSpan();
- this.width = array.GetLength(1);
- this.offset = column - this.width;
-#else
- this.array = array;
- this.column = column;
- this.height = array.GetLength(0);
- this.row = -1;
-#endif
- }
-
- ///
- /// Implements the duck-typed method.
- ///
- /// whether a new element is available, otherwise
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
-#if SPAN_RUNTIME_SUPPORT
- int offset = this.offset + this.width;
-
- if ((uint)offset < (uint)this.span.Length)
- {
- this.offset = offset;
-
- return true;
- }
-#else
- int row = this.row + 1;
-
- if (row < this.height)
- {
- this.row = row;
-
- return true;
- }
-#endif
- return false;
- }
-
- ///
- /// Gets the duck-typed property.
- ///
- public ref T Current
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
-#if SPAN_RUNTIME_SUPPORT
- return ref this.span.DangerousGetReferenceAt(this.offset);
-#else
- return ref this.array[this.row, this.column];
-#endif
- }
- }
- }
-
- ///
- /// Throws an when the is invalid.
- ///
- private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
- {
- throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
- }
- }
-}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs
deleted file mode 100644
index 5c146d5a5e8..00000000000
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/Array2DRowEnumerable{T}.cs
+++ /dev/null
@@ -1,170 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if !SPAN_RUNTIME_SUPPORT
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-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.
- [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
- {
- // This type is never used on .NET Core runtimes, where
- // the fast indexer is available. Therefore, we can just
- // use the built-in indexer for 2D arrays to access the value.
- return ref this.array[this.row, this.column];
- }
- }
- }
-
- ///
- /// Throws an when the is invalid.
- ///
- private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
- {
- throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
- }
- }
-}
-
-#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs
new file mode 100644
index 00000000000..2eff0897662
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs
@@ -0,0 +1,371 @@
+// 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;
+#if SPAN_RUNTIME_SUPPORT
+using System.Runtime.InteropServices;
+#endif
+using Microsoft.Toolkit.HighPerformance.Extensions;
+using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+#if !SPAN_RUNTIME_SUPPORT
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
+#endif
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that iterates readonly items from arbitrary memory locations.
+ ///
+ /// The type of items to enumerate.
+ public readonly ref struct ReadOnlyRefEnumerable
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// The instance pointing to the first item in the target memory area.
+ ///
+ /// The field maps to the total available length.
+ private readonly ReadOnlySpan span;
+#else
+ ///
+ /// The target instance, if present.
+ ///
+ private readonly object? instance;
+
+ ///
+ /// The initial offset within .
+ ///
+ private readonly IntPtr offset;
+
+ ///
+ /// The total available length for the sequence.
+ ///
+ private readonly int length;
+#endif
+
+ ///
+ /// The distance between items in the sequence to enumerate.
+ ///
+ /// The distance refers to items, not byte offset.
+ private readonly int step;
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The instance pointing to the first item in the target memory area.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ReadOnlyRefEnumerable(ReadOnlySpan span, int step)
+ {
+ this.span = span;
+ this.step = step;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// A reference to the first item of the sequence.
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ReadOnlyRefEnumerable(in T reference, int length, int step)
+ {
+ this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length);
+ this.step = step;
+ }
+#else
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The target instance.
+ /// The initial offset within .
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step)
+ {
+ this.instance = instance;
+ this.offset = offset;
+ this.length = length;
+ this.step = step;
+ }
+#endif
+
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ return new Enumerator(this.span, this.step);
+#else
+ return new Enumerator(this.instance, this.offset, this.length, this.step);
+#endif
+ }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ public void CopyTo(RefEnumerable destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.step == 1)
+ {
+ destination.CopyFrom(this.span);
+
+ return;
+ }
+
+ if (destination.Step == 1)
+ {
+ CopyTo(destination.Span);
+
+ return;
+ }
+
+ ref T sourceRef = ref this.span.DangerousGetReference();
+ ref T destinationRef = ref destination.Span.DangerousGetReference();
+ int
+ sourceLength = this.span.Length,
+ destinationLength = destination.Span.Length;
+#else
+ ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset);
+ ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset);
+ int
+ sourceLength = this.length,
+ destinationLength = destination.Length;
+#endif
+
+ if ((uint)destinationLength < (uint)sourceLength)
+ {
+ ThrowArgumentExceptionForDestinationTooShort();
+ }
+
+ RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step, (nint)(uint)destination.Step);
+ }
+
+ ///
+ /// Attempts to copy the current instance to a destination .
+ ///
+ /// The target of the copy operation.
+ /// Whether or not the operation was successful.
+ public bool TryCopyTo(RefEnumerable destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int
+ sourceLength = this.span.Length,
+ destinationLength = destination.Span.Length;
+#else
+ int
+ sourceLength = this.length,
+ destinationLength = destination.Length;
+#endif
+
+ if (destinationLength >= sourceLength)
+ {
+ CopyTo(destination);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ public void CopyTo(Span destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.step == 1)
+ {
+ this.span.CopyTo(destination);
+
+ return;
+ }
+
+ ref T sourceRef = ref this.span.DangerousGetReference();
+ int length = this.span.Length;
+#else
+ ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset);
+ int length = this.length;
+#endif
+ if ((uint)destination.Length < (uint)length)
+ {
+ ThrowArgumentExceptionForDestinationTooShort();
+ }
+
+ ref T destinationRef = ref destination.DangerousGetReference();
+
+ RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step);
+ }
+
+ ///
+ /// Attempts to copy the current instance to a destination .
+ ///
+ /// The target of the copy operation.
+ /// Whether or not the operation was successful.
+ public bool TryCopyTo(Span destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int length = this.span.Length;
+#else
+ int length = this.length;
+#endif
+
+ if (destination.Length >= length)
+ {
+ CopyTo(destination);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ [Pure]
+ public T[] ToArray()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int length = this.span.Length;
+#else
+ int length = this.length;
+#endif
+
+ // Empty array if no data is mapped
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ T[] array = new T[length];
+
+ CopyTo(array);
+
+ return array;
+ }
+
+ ///
+ /// Implicitly converts a instance into a one.
+ ///
+ /// The input instance.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumerable)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ return new ReadOnlyRefEnumerable(enumerable.Span, enumerable.Step);
+#else
+ return new ReadOnlyRefEnumerable(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
+#endif
+ }
+
+ ///
+ /// A custom enumerator type to traverse items within a instance.
+ ///
+ public ref struct Enumerator
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ private readonly ReadOnlySpan span;
+#else
+ ///
+ private readonly object? instance;
+
+ ///
+ private readonly IntPtr offset;
+
+ ///
+ private readonly int length;
+#endif
+
+ ///
+ private readonly int step;
+
+ ///
+ /// The current position in the sequence.
+ ///
+ private int position;
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The instance with the info on the items to traverse.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(ReadOnlySpan span, int step)
+ {
+ this.span = span;
+ this.step = step;
+ this.position = -1;
+ }
+#else
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The target instance.
+ /// The initial offset within .
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(object? instance, IntPtr offset, int length, int step)
+ {
+ this.instance = instance;
+ this.offset = offset;
+ this.length = length;
+ this.step = step;
+ this.position = -1;
+ }
+#endif
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ return ++this.position < this.span.Length;
+#else
+ return ++this.position < this.length;
+#endif
+ }
+
+ ///
+ public readonly ref readonly T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ref T r0 = ref this.span.DangerousGetReference();
+#else
+ ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset);
+#endif
+ nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
+ ref T ri = ref Unsafe.Add(ref r0, offset);
+
+ return ref ri;
+ }
+ }
+ }
+
+ ///
+ /// Throws an when the target span is too short.
+ ///
+ private static void ThrowArgumentExceptionForDestinationTooShort()
+ {
+ throw new ArgumentException("The target span is too short to copy all the current items to");
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
index c732861d670..6a0724ed01e 100644
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanEnumerable{T}.cs
@@ -54,16 +54,7 @@ public ReadOnlySpanEnumerable(ReadOnlySpan span)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
- int newIndex = this.index + 1;
-
- if (newIndex < this.span.Length)
- {
- this.index = newIndex;
-
- return true;
- }
-
- return false;
+ return ++this.index < this.span.Length;
}
///
@@ -76,7 +67,7 @@ public readonly Item Current
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
- ref T ri = ref Unsafe.Add(ref r0, this.index);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
// See comment in SpanEnumerable about this
return new Item(ref ri, this.index);
@@ -139,7 +130,7 @@ public ref readonly T Value
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);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
return ref ri;
#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs
index 37bc6168f10..a4a4dd7c4aa 100644
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlySpanTokenizer{T}.cs
@@ -43,6 +43,7 @@ public ref struct ReadOnlySpanTokenizer
///
/// The source instance.
/// The separator item to use.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator)
{
this.span = span;
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs
new file mode 100644
index 00000000000..fae37dc76fc
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs
@@ -0,0 +1,464 @@
+// 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;
+#if SPAN_RUNTIME_SUPPORT
+using System.Runtime.InteropServices;
+#endif
+using Microsoft.Toolkit.HighPerformance.Extensions;
+using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+#if !SPAN_RUNTIME_SUPPORT
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
+#endif
+
+namespace Microsoft.Toolkit.HighPerformance.Enumerables
+{
+ ///
+ /// A that iterates items from arbitrary memory locations.
+ ///
+ /// The type of items to enumerate.
+ public readonly ref struct RefEnumerable
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// The instance pointing to the first item in the target memory area.
+ ///
+ /// The field maps to the total available length.
+ internal readonly Span Span;
+#else
+ ///
+ /// The target instance, if present.
+ ///
+ internal readonly object? Instance;
+
+ ///
+ /// The initial offset within .
+ ///
+ internal readonly IntPtr Offset;
+
+ ///
+ /// The total available length for the sequence.
+ ///
+ internal readonly int Length;
+#endif
+
+ ///
+ /// The distance between items in the sequence to enumerate.
+ ///
+ /// The distance refers to items, not byte offset.
+ internal readonly int Step;
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// A reference to the first item of the sequence.
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal RefEnumerable(ref T reference, int length, int step)
+ {
+ Span = MemoryMarshal.CreateSpan(ref reference, length);
+ Step = step;
+ }
+#else
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The target instance.
+ /// The initial offset within .
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
+ {
+ Instance = instance;
+ Offset = offset;
+ Length = length;
+ Step = step;
+ }
+#endif
+
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Enumerator GetEnumerator()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ return new Enumerator(this.Span, this.Step);
+#else
+ return new Enumerator(this.Instance, this.Offset, this.Length, this.Step);
+#endif
+ }
+
+ ///
+ /// Clears the contents of the current instance.
+ ///
+ public void Clear()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ // Fast path for contiguous items
+ if (this.Step == 1)
+ {
+ this.Span.Clear();
+
+ return;
+ }
+
+ ref T r0 = ref this.Span.DangerousGetReference();
+ int length = this.Span.Length;
+#else
+ ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
+ int length = this.Length;
+#endif
+
+ RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step);
+ }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ public void CopyTo(RefEnumerable destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.Step == 1)
+ {
+ destination.CopyFrom(this.Span);
+
+ return;
+ }
+
+ if (destination.Step == 1)
+ {
+ CopyTo(destination.Span);
+
+ return;
+ }
+
+ ref T sourceRef = ref this.Span.DangerousGetReference();
+ ref T destinationRef = ref destination.Span.DangerousGetReference();
+ int
+ sourceLength = this.Span.Length,
+ destinationLength = destination.Span.Length;
+#else
+ ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
+ ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset);
+ int
+ sourceLength = this.Length,
+ destinationLength = destination.Length;
+#endif
+
+ if ((uint)destinationLength < (uint)sourceLength)
+ {
+ ThrowArgumentExceptionForDestinationTooShort();
+ }
+
+ RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step);
+ }
+
+ ///
+ /// Attempts to copy the current instance to a destination .
+ ///
+ /// The target of the copy operation.
+ /// Whether or not the operation was successful.
+ public bool TryCopyTo(RefEnumerable destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int
+ sourceLength = this.Span.Length,
+ destinationLength = destination.Span.Length;
+#else
+ int
+ sourceLength = this.Length,
+ destinationLength = destination.Length;
+#endif
+
+ if (destinationLength >= sourceLength)
+ {
+ CopyTo(destination);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ public void CopyTo(Span destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.Step == 1)
+ {
+ this.Span.CopyTo(destination);
+
+ return;
+ }
+
+ ref T sourceRef = ref this.Span.DangerousGetReference();
+ int length = this.Span.Length;
+#else
+ ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
+ int length = this.Length;
+#endif
+ if ((uint)destination.Length < (uint)length)
+ {
+ ThrowArgumentExceptionForDestinationTooShort();
+ }
+
+ ref T destinationRef = ref destination.DangerousGetReference();
+
+ RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step);
+ }
+
+ ///
+ /// Attempts to copy the current instance to a destination .
+ ///
+ /// The target of the copy operation.
+ /// Whether or not the operation was successful.
+ public bool TryCopyTo(Span destination)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int length = this.Span.Length;
+#else
+ int length = this.Length;
+#endif
+
+ if (destination.Length >= length)
+ {
+ CopyTo(destination);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Copies the contents of a source into the current instance.
+ ///
+ /// The source instance.
+ ///
+ /// Thrown when the current is shorter than the source instance.
+ ///
+ internal void CopyFrom(ReadOnlySpan source)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.Step == 1)
+ {
+ source.CopyTo(this.Span);
+
+ return;
+ }
+
+ ref T destinationRef = ref this.Span.DangerousGetReference();
+ int destinationLength = this.Span.Length;
+#else
+ ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
+ int destinationLength = this.Length;
+#endif
+ ref T sourceRef = ref source.DangerousGetReference();
+ int sourceLength = source.Length;
+
+ if ((uint)destinationLength < (uint)sourceLength)
+ {
+ ThrowArgumentExceptionForDestinationTooShort();
+ }
+
+ RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step);
+ }
+
+ ///
+ /// Attempts to copy the source into the current instance.
+ ///
+ /// The source instance.
+ /// Whether or not the operation was successful.
+ public bool TryCopyFrom(ReadOnlySpan source)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ int length = this.Span.Length;
+#else
+ int length = this.Length;
+#endif
+
+ if (length >= source.Length)
+ {
+ CopyFrom(source);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Fills the elements of this with a specified value.
+ ///
+ /// The value to assign to each element of the instance.
+ public void Fill(T value)
+ {
+#if SPAN_RUNTIME_SUPPORT
+ if (this.Step == 1)
+ {
+ this.Span.Fill(value);
+
+ return;
+ }
+
+ ref T r0 = ref this.Span.DangerousGetReference();
+ int length = this.Span.Length;
+#else
+ ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
+ int length = this.Length;
+#endif
+
+ RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value);
+ }
+
+ ///
+ /// 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 SPAN_RUNTIME_SUPPORT
+ int length = this.Span.Length;
+#else
+ int length = this.Length;
+#endif
+
+ // Empty array if no data is mapped
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ T[] array = new T[length];
+
+ CopyTo(array);
+
+ return array;
+ }
+
+ ///
+ /// A custom enumerator type to traverse items within a instance.
+ ///
+ public ref struct Enumerator
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ private readonly Span span;
+#else
+ ///
+ private readonly object? instance;
+
+ ///
+ private readonly IntPtr offset;
+
+ ///
+ private readonly int length;
+#endif
+
+ ///
+ private readonly int step;
+
+ ///
+ /// The current position in the sequence.
+ ///
+ private int position;
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The instance with the info on the items to traverse.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(Span span, int step)
+ {
+ this.span = span;
+ this.step = step;
+ this.position = -1;
+ }
+#else
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The target instance.
+ /// The initial offset within .
+ /// The number of items in the sequence.
+ /// The distance between items in the sequence to enumerate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Enumerator(object? instance, IntPtr offset, int length, int step)
+ {
+ this.instance = instance;
+ this.offset = offset;
+ this.length = length;
+ this.step = step;
+ this.position = -1;
+ }
+#endif
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
+ {
+#if SPAN_RUNTIME_SUPPORT
+ return ++this.position < this.span.Length;
+#else
+ return ++this.position < this.length;
+#endif
+ }
+
+ ///
+ public readonly ref T Current
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+#if SPAN_RUNTIME_SUPPORT
+ ref T r0 = ref this.span.DangerousGetReference();
+#else
+ ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset);
+#endif
+
+ // Here we just offset by shifting down as if we were traversing a 2D array with a
+ // a single column, with the width of each row represented by the step, the height
+ // represented by the current position, and with only the first element of each row
+ // being inspected. We can perform all the indexing operations in this type as nint,
+ // as the maximum offset is guaranteed never to exceed the maximum value, since on
+ // 32 bit architectures it's not possible to allocate that much memory anyway.
+ nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
+ ref T ri = ref Unsafe.Add(ref r0, offset);
+
+ return ref ri;
+ }
+ }
+ }
+
+ ///
+ /// Throws an when the target span is too short.
+ ///
+ private static void ThrowArgumentExceptionForDestinationTooShort()
+ {
+ throw new ArgumentException("The target span is too short to copy all the current items to");
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs
index 23cbffac668..618f1419ace 100644
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs
@@ -54,16 +54,7 @@ public SpanEnumerable(Span span)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
- int newIndex = this.index + 1;
-
- if (newIndex < this.span.Length)
- {
- this.index = newIndex;
-
- return true;
- }
-
- return false;
+ return ++this.index < this.span.Length;
}
///
@@ -76,7 +67,7 @@ public readonly Item Current
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
- ref T ri = ref Unsafe.Add(ref r0, this.index);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
// support for the Span types), we can save 4 bytes by piggybacking the
@@ -144,7 +135,7 @@ public ref T Value
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);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
return ref ri;
#endif
diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs
index da8a9dce040..b5673f2e1c4 100644
--- a/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Enumerables/SpanTokenizer{T}.cs
@@ -43,6 +43,7 @@ public ref struct SpanTokenizer
///
/// The source instance.
/// The separator item to use.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanTokenizer(Span span, T separator)
{
this.span = span;
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs
similarity index 81%
rename from Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
rename to Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs
index 73e3b8ed73f..887cdd0f989 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.1D.cs
@@ -10,6 +10,7 @@
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@@ -35,20 +36,9 @@ public static ref T DangerousGetReference(this T[] array)
return ref r0;
#else
-#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
- // Checking the length of the array like so allows the JIT
- // to skip its own bounds check, which results in the element
- // access below to be executed without branches.
- if (0u < (uint)array.Length)
- {
- return ref array[0];
- }
+ IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset();
- unsafe
- {
- return ref Unsafe.AsRef(null);
- }
-#pragma warning restore SA1131
+ return ref array.DangerousGetObjectDataReferenceAt(offset);
#endif
}
@@ -62,21 +52,20 @@ public static ref T DangerousGetReference(this T[] array)
/// 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 unsafe ref T DangerousGetReferenceAt(this T[] array, int i)
+ public static ref T DangerousGetReferenceAt(this T[] array, int i)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As(array);
ref T r0 = ref Unsafe.As(ref arrayData.Data);
- ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
#else
- if ((uint)i < (uint)array.Length)
- {
- return ref array[i];
- }
+ IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset();
+ ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
- return ref Unsafe.AsRef(null);
+ return ref ri;
#endif
}
@@ -111,13 +100,20 @@ private sealed class RawArrayData
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int Count(this T[] array, T value)
+ public static int Count(this T[] array, T value)
where T : IEquatable
{
ref T r0 = ref array.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)array.Length;
+ nint
+ length = RuntimeHelpers.GetArrayNativeLength(array),
+ count = SpanHelper.Count(ref r0, length, value);
+
+ if ((nuint)count > int.MaxValue)
+ {
+ ThrowOverflowException();
+ }
- return SpanHelper.Count(ref r0, length, value);
+ return (int)count;
}
///
@@ -182,13 +178,34 @@ public static SpanTokenizer Tokenize(this T[] array, T separator)
/// The Djb2 hash is fully deterministic and with no random components.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int GetDjb2HashCode(this T[] array)
+ public static int GetDjb2HashCode(this T[] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)array.Length;
+ nint length = RuntimeHelpers.GetArrayNativeLength(array);
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
+
+ ///
+ /// Checks whether or not a given array is covariant.
+ ///
+ /// The type of items in the input array instance.
+ /// The input array instance.
+ /// Whether or not is covariant.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsCovariant(this T[] array)
+ {
+ return default(T) is null && array.GetType() != typeof(T[]);
+ }
+
+ ///
+ /// Throws an when the "column" parameter is invalid.
+ ///
+ public static void ThrowOverflowException()
+ {
+ throw new OverflowException();
+ }
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
index a7a4baa075d..8807c558b33 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.2D.cs
@@ -4,13 +4,15 @@
using System;
using System.Diagnostics.Contracts;
-using System.Drawing;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+using Microsoft.Toolkit.HighPerformance.Memory;
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@@ -36,17 +38,9 @@ public static ref T DangerousGetReference(this T[,] array)
return ref r0;
#else
-#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
- if (0u < (uint)array.Length)
- {
- return ref array[0, 0];
- }
+ IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset();
- unsafe
- {
- return ref Unsafe.AsRef(null);
- }
-#pragma warning restore SA1131
+ return ref array.DangerousGetObjectDataReferenceAt(offset);
#endif
}
@@ -66,23 +60,23 @@ public static ref T DangerousGetReference(this T[,] array)
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe ref T DangerousGetReferenceAt(this T[,] array, int i, int j)
+ public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As(array);
- int offset = (i * arrayData.Width) + j;
+ nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
ref T r0 = ref Unsafe.As(ref arrayData.Data);
- ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);
+ ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
#else
- if ((uint)i < (uint)array.GetLength(0) &&
- (uint)j < (uint)array.GetLength(1))
- {
- return ref array[i, j];
- }
+ int width = array.GetLength(1);
+ nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j;
+ IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset();
+ ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset);
+ ref T ri = ref Unsafe.Add(ref r0, index);
- return ref Unsafe.AsRef(null);
+ return ref ri;
#endif
}
@@ -112,95 +106,46 @@ private sealed class RawArray2DData
#endif
///
- /// 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 SPAN_RUNTIME_SUPPORT
-#if NETCORE_RUNTIME
- ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
-#else
- ref T r0 = ref array[i, bounds.Left];
-#endif
-
- // Span.Fill will use vectorized instructions when possible
- MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
-#else
- ref T r0 = ref array[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.
+ /// 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 .
+ /// A with the items from the target row within .
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ /// Thrown when one of the input parameters is out of range.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static
-#if SPAN_RUNTIME_SUPPORT
- 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)
+ public static RefEnumerable GetRow(this T[,] array, int row)
{
- if ((uint)row >= (uint)array.GetLength(0))
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ int height = array.GetLength(0);
+
+ if ((uint)row >= (uint)height)
{
- throw new ArgumentOutOfRangeException(nameof(row));
+ ThrowArgumentOutOfRangeExceptionForRow();
}
+ int width = array.GetLength(1);
+
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
- return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
+ return new RefEnumerable(ref r0, width, 1);
#else
- return new Array2DRowEnumerable(array, row);
+ ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
+ IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
+
+ return new RefEnumerable(array, offset, width, 1);
#endif
}
///
- /// Returns an enumerable that returns the items from a given column in a given 2D array instance.
+ /// Returns a 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 =
@@ -221,15 +166,196 @@ public static
/// 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.
+ /// The returned value shouldn't be used directly: use this extension in a loop.
+ /// Thrown when one of the input parameters is out of range.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static RefEnumerable GetColumn(this T[,] array, int column)
+ {
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ int width = array.GetLength(1);
+
+ if ((uint)column >= (uint)width)
+ {
+ ThrowArgumentOutOfRangeExceptionForColumn();
+ }
+
+ int height = array.GetLength(0);
+
+#if SPAN_RUNTIME_SUPPORT
+ ref T r0 = ref array.DangerousGetReferenceAt(0, column);
+
+ return new RefEnumerable(ref r0, height, width);
+#else
+ ref T r0 = ref array.DangerousGetReferenceAt(0, column);
+ IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
+
+ return new RefEnumerable(array, offset, height, width);
+#endif
+ }
+
+ ///
+ /// Creates 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 Span2D AsSpan2D(this T[,]? array)
+ {
+ return new Span2D(array);
+ }
+
+ ///
+ /// Creates a new over an input 2D array.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input 2D array instance.
+ /// The target row to map within .
+ /// The target column to map within .
+ /// The height to map within .
+ /// The width to map within .
+ ///
+ /// Thrown when doesn't match .
+ ///
+ ///
+ /// Thrown when either , or
+ /// are negative or not within the bounds that are valid for .
+ ///
+ /// A instance with the values of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span2D AsSpan2D(this T[,]? array, int row, int column, int height, int width)
+ {
+ return new Span2D(array, row, column, height, width);
+ }
+
+ ///
+ /// Creates 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 Array2DColumnEnumerable GetColumn(this T[,] array, int column)
+ public static Memory2D AsMemory2D(this T[,]? array)
{
- return new Array2DColumnEnumerable(array, column);
+ return new Memory2D(array);
+ }
+
+ ///
+ /// Creates a new over an input 2D array.
+ ///
+ /// The type of elements in the input 2D array instance.
+ /// The input 2D array instance.
+ /// The target row to map within .
+ /// The target column to map within .
+ /// The height to map within .
+ /// The width to map within .
+ ///
+ /// Thrown when doesn't match .
+ ///
+ ///
+ /// Thrown when either , or
+ /// are negative or not within the bounds that are valid for .
+ ///
+ /// A instance with the values of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory2D AsMemory2D(this T[,]? array, int row, int column, int height, int width)
+ {
+ return new Memory2D(array, row, column, height, width);
}
#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// 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 .
+ /// Thrown when doesn't match .
+ /// Thrown when is invalid.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span GetRowSpan(this T[,] array, int row)
+ {
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ if ((uint)row >= (uint)array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForRow();
+ }
+
+ ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
+
+ return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
+ }
+
+ ///
+ /// 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 .
+ /// Thrown when doesn't match .
+ /// Thrown when is invalid.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory GetRowMemory(this T[,] array, int row)
+ {
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ if ((uint)row >= (uint)array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForRow();
+ }
+
+ ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
+ IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
+
+ return new RawObjectMemoryManager(array, offset, array.GetLength(1)).Memory;
+ }
+
+ ///
+ /// Creates 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 Memory AsMemory(this T[,]? array)
+ {
+ if (array is null)
+ {
+ return default;
+ }
+
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset();
+ int length = array.Length;
+
+ return new RawObjectMemoryManager(array, offset, length).Memory;
+ }
+
///
/// Creates a new over an input 2D array.
///
@@ -238,26 +364,21 @@ public static Array2DColumnEnumerable GetColumn(this T[,] array, int colum
/// A instance with the values of .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span AsSpan(this T[,] array)
+ public static Span AsSpan(this T[,]? array)
{
-#if NETCORE_RUNTIME
- var arrayData = Unsafe.As(array);
+ if (array is null)
+ {
+ return default;
+ }
- // 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);
-#else
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ ref T r0 = ref array.DangerousGetReference();
int length = array.Length;
- ref T r0 = ref array[0, 0];
-#endif
+
return MemoryMarshal.CreateSpan(ref r0, length);
}
#endif
@@ -275,9 +396,16 @@ public static unsafe int Count(this T[,] array, T value)
where T : IEquatable
{
ref T r0 = ref array.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)array.Length;
+ nint
+ length = RuntimeHelpers.GetArrayNativeLength(array),
+ count = SpanHelper.Count(ref r0, length, value);
+
+ if ((nuint)count > int.MaxValue)
+ {
+ ThrowOverflowException();
+ }
- return SpanHelper.Count(ref r0, length, value);
+ return (int)count;
}
///
@@ -294,9 +422,46 @@ public static unsafe int GetDjb2HashCode(this T[,] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)array.Length;
+ nint length = RuntimeHelpers.GetArrayNativeLength(array);
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
+
+ ///
+ /// Checks whether or not a given array is covariant.
+ ///
+ /// The type of items in the input array instance.
+ /// The input array instance.
+ /// Whether or not is covariant.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsCovariant(this T[,] array)
+ {
+ return default(T) is null && array.GetType() != typeof(T[,]);
+ }
+
+ ///
+ /// Throws an when using an array of an invalid type.
+ ///
+ private static void ThrowArrayTypeMismatchException()
+ {
+ throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
+ }
+
+ ///
+ /// Throws an when the "row" parameter is invalid.
+ ///
+ public static void ThrowArgumentOutOfRangeExceptionForRow()
+ {
+ throw new ArgumentOutOfRangeException("row");
+ }
+
+ ///
+ /// Throws an when the "column" parameter is invalid.
+ ///
+ public static void ThrowArgumentOutOfRangeExceptionForColumn()
+ {
+ throw new ArgumentOutOfRangeException("column");
+ }
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs
new file mode 100644
index 00000000000..846e3c5eadb
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.3D.cs
@@ -0,0 +1,324 @@
+// 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;
+#if SPAN_RUNTIME_SUPPORT
+using System.Runtime.InteropServices;
+using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
+#endif
+using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+using Microsoft.Toolkit.HighPerformance.Memory;
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
+
+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 3D array, with no bounds checks.
+ ///
+ /// The type of elements in the input 3D 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)
+ {
+#if NETCORE_RUNTIME
+ var arrayData = Unsafe.As(array);
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+
+ return ref r0;
+#else
+ IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
+
+ return ref array.DangerousGetObjectDataReferenceAt(offset);
+#endif
+ }
+
+ ///
+ /// Returns a reference to an element at a specified coordinate within a given 3D array, with no bounds checks.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The input 2D array instance.
+ /// The depth index of the element to retrieve within .
+ /// 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, int k)
+ {
+#if NETCORE_RUNTIME
+ var arrayData = Unsafe.As(array);
+ nint offset =
+ ((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
+ ((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
+ ref T r0 = ref Unsafe.As(ref arrayData.Data);
+ ref T ri = ref Unsafe.Add(ref r0, offset);
+
+ return ref ri;
+#else
+ int
+ height = array.GetLength(1),
+ width = array.GetLength(2);
+ nint index =
+ ((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
+ ((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
+ IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
+ ref T r0 = ref array.DangerousGetObjectDataReferenceAt(offset);
+ ref T ri = ref Unsafe.Add(ref r0, index);
+
+ return ref ri;
+#endif
+ }
+
+#if NETCORE_RUNTIME
+ // See description for this in the 2D partial file.
+ // Using the CHW naming scheme here (like with RGB images).
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class RawArray3DData
+ {
+#pragma warning disable CS0649 // Unassigned fields
+#pragma warning disable SA1401 // Fields should be private
+ public IntPtr Length;
+ public int Channel;
+ public int Height;
+ public int Width;
+ public int ChannelLowerBound;
+ public int HeightLowerBound;
+ public int WidthLowerBound;
+ public byte Data;
+#pragma warning restore CS0649
+#pragma warning restore SA1401
+ }
+#endif
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Creates a new over an input 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The input 3D array instance.
+ /// A instance with the values of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory AsMemory(this T[,,]? array)
+ {
+ if (array is null)
+ {
+ return default;
+ }
+
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
+ int length = array.Length;
+
+ return new RawObjectMemoryManager(array, offset, length).Memory;
+ }
+
+ ///
+ /// Creates a new over an input 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The input 3D array instance.
+ /// A instance with the values of .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(this T[,,]? array)
+ {
+ if (array is null)
+ {
+ return default;
+ }
+
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ ref T r0 = ref array.DangerousGetReference();
+ int length = array.Length;
+
+ return MemoryMarshal.CreateSpan(ref r0, length);
+ }
+
+ ///
+ /// Creates a new instance of the struct wrapping a layer in a 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The given 3D array to wrap.
+ /// The target layer to map within .
+ /// Thrown when doesn't match .
+ /// Thrown when is invalid.
+ /// A instance wrapping the target layer within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpan(this T[,,] array, int depth)
+ {
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ if ((uint)depth >= (uint)array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForDepth();
+ }
+
+ ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
+ int length = checked(array.GetLength(1) * array.GetLength(2));
+
+ return MemoryMarshal.CreateSpan(ref r0, length);
+ }
+
+ ///
+ /// Creates a new instance of the struct wrapping a layer in a 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The given 3D array to wrap.
+ /// The target layer to map within .
+ /// Thrown when doesn't match .
+ /// Thrown when is invalid.
+ /// A instance wrapping the target layer within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory AsMemory(this T[,,] array, int depth)
+ {
+ if (array.IsCovariant())
+ {
+ ThrowArrayTypeMismatchException();
+ }
+
+ if ((uint)depth >= (uint)array.GetLength(0))
+ {
+ ThrowArgumentOutOfRangeExceptionForDepth();
+ }
+
+ ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
+ IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
+ int length = checked(array.GetLength(1) * array.GetLength(2));
+
+ return new RawObjectMemoryManager(array, offset, length).Memory;
+ }
+#endif
+
+ ///
+ /// Creates a new instance of the struct wrapping a layer in a 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The given 3D array to wrap.
+ /// The target layer to map within .
+ ///
+ /// Thrown when doesn't match .
+ ///
+ /// Thrown when either is invalid.
+ /// A instance wrapping the target layer within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span2D AsSpan2D(this T[,,] array, int depth)
+ {
+ return new Span2D(array, depth);
+ }
+
+ ///
+ /// Creates a new instance of the struct wrapping a layer in a 3D array.
+ ///
+ /// The type of elements in the input 3D array instance.
+ /// The given 3D array to wrap.
+ /// The target layer to map within .
+ ///
+ /// Thrown when doesn't match .
+ ///
+ /// Thrown when either is invalid.
+ /// A instance wrapping the target layer within .
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory2D AsMemory2D(this T[,,] array, int depth)
+ {
+ return new Memory2D(array, depth);
+ }
+
+ ///
+ /// Counts the number of occurrences of a given value into a target 3D array instance.
+ ///
+ /// The type of items in the input 3D array instance.
+ /// The input 3D 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
+ {
+ ref T r0 = ref array.DangerousGetReference();
+ nint
+ length = RuntimeHelpers.GetArrayNativeLength(array),
+ count = SpanHelper.Count(ref r0, length, value);
+
+ if ((nuint)count > int.MaxValue)
+ {
+ ThrowOverflowException();
+ }
+
+ return (int)count;
+ }
+
+ ///
+ /// Gets a content hash from the input 3D array instance using the Djb2 algorithm.
+ /// For more info, see the documentation for .
+ ///
+ /// The type of items in the input 3D array instance.
+ /// The input 3D array instance.
+ /// The Djb2 value for the input 3D 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
+ {
+ ref T r0 = ref array.DangerousGetReference();
+ nint length = RuntimeHelpers.GetArrayNativeLength(array);
+
+ return SpanHelper.GetDjb2HashCode(ref r0, length);
+ }
+
+ ///
+ /// Checks whether or not a given array is covariant.
+ ///
+ /// The type of items in the input array instance.
+ /// The input array instance.
+ /// Whether or not is covariant.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsCovariant(this T[,,] array)
+ {
+ return default(T) is null && array.GetType() != typeof(T[,,]);
+ }
+
+ ///
+ /// Throws an when the "depth" parameter is invalid.
+ ///
+ public static void ThrowArgumentOutOfRangeExceptionForDepth()
+ {
+ throw new ArgumentOutOfRangeException("depth");
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs
index 19e5c258619..95040b5689c 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/BoolExtensions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
@@ -12,6 +13,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
///
public static class BoolExtensions
{
+ ///
+ /// Converts the given value into a .
+ ///
+ /// The input value to convert.
+ /// 1 if is , 0 otherwise.
+ /// This method does not contain branching instructions.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ToByte(this bool flag)
+ {
+ return Unsafe.As(ref flag);
+ }
+
///
/// Converts the given value into an .
///
@@ -20,6 +34,7 @@ public static class BoolExtensions
/// This method does not contain branching instructions.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [Obsolete("Use ToByte instead.")]
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
index c4c468162fb..2dfab655d8d 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/HashCodeExtensions.cs
@@ -23,12 +23,7 @@ public static class HashCodeExtensions
/// The input instance.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add(ref this HashCode hashCode, ReadOnlySpan span)
-#if SPAN_RUNTIME_SUPPORT
where T : notnull
-#else
- // Same type constraints as HashCode, see comments there
- where T : unmanaged
-#endif
{
int hash = HashCode.CombineValues(span);
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
index 794339fbbed..e2b9d434cad 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/MemoryExtensions.cs
@@ -6,6 +6,9 @@
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
+#if SPAN_RUNTIME_SUPPORT
+using Microsoft.Toolkit.HighPerformance.Memory;
+#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@@ -15,6 +18,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
///
public static class MemoryExtensions
{
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory2D AsMemory2D(this Memory memory, int height, int width)
+ {
+ return new Memory2D(memory, height, width);
+ }
+
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The initial offset within .
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The pitch in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Memory2D AsMemory2D(this Memory memory, int offset, int height, int width, int pitch)
+ {
+ return new Memory2D(memory, offset, height, width, pitch);
+ }
+#endif
+
///
/// Returns a wrapping the contents of the given of instance.
///
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
index eab50592776..0c1c6eb821f 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlyMemoryExtensions.cs
@@ -5,6 +5,10 @@
using System;
using System.Diagnostics.Contracts;
using System.IO;
+using System.Runtime.CompilerServices;
+#if SPAN_RUNTIME_SUPPORT
+using Microsoft.Toolkit.HighPerformance.Memory;
+#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@@ -14,6 +18,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
///
public static class ReadOnlyMemoryExtensions
{
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int height, int width)
+ {
+ return new ReadOnlyMemory2D(memory, height, width);
+ }
+
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The initial offset within .
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The pitch in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlyMemory2D AsMemory2D(this ReadOnlyMemory memory, int offset, int height, int width, int pitch)
+ {
+ return new ReadOnlyMemory2D(memory, offset, height, width, pitch);
+ }
+#endif
+
///
/// Returns a wrapping the contents of the given of instance.
///
@@ -27,6 +77,7 @@ public static class ReadOnlyMemoryExtensions
///
/// Thrown when has an invalid data store.
[Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ReadOnlyMemory memory)
{
return MemoryStream.Create(memory, true);
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs
index 47428db40a2..b4264395133 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs
@@ -8,6 +8,9 @@
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+#if SPAN_RUNTIME_SUPPORT
+using Microsoft.Toolkit.HighPerformance.Memory;
+#endif
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@@ -40,10 +43,10 @@ public static ref T DangerousGetReference(this ReadOnlySpan span)
/// 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 unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i)
+ public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i)
{
- // Here we assume the input index will never be negative, so we do an unsafe cast to
- // force the JIT to skip the sign extension when going from int to native int.
+ // Here we assume the input index will never be negative, so we do a (nint)(uint) cast
+ // to force the JIT to skip the sign extension when going from int to native int.
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
// =============================
// L0000: mov rax, [rcx]
@@ -54,7 +57,7 @@ public static unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span,
// Note the movsxd (move with sign extension) to expand the index passed in edx to
// the whole rdx register. This is unnecessary and more expensive than just a mov,
// which when done to a large register size automatically zeroes the upper bits.
- // With the (IntPtr)(void*)(uint) cast, we get the following codegen instead:
+ // With the (nint)(uint) cast, we get the following codegen instead:
// =============================
// L0000: mov rax, [rcx]
// L0003: mov edx, edx
@@ -67,13 +70,31 @@ public static unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span,
// bit architectures, producing optimal code in both cases (they are either completely
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
- // then cast to void*, which lets the following IntPtr cast avoid the range check on 32 bit
- // (since uint could be out of range there if the original index was negative). The final
- // result is a clean mov as shown above. This will eventually be natively supported by the
- // JIT compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
+ // then cast to nint, so that we can obtain an IntPtr value without the range check (since
+ // uint could be out of range there if the original index was negative). The final result
+ // is a clean mov as shown above. This will eventually be natively supported by the JIT
+ // compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
ref T r0 = ref MemoryMarshal.GetReference(span);
- ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
+
+ return ref ri;
+ }
+
+ ///
+ /// 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, nint i)
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
@@ -117,7 +138,7 @@ public static unsafe ref T DangerousGetReferenceAt(this ReadOnlySpan span,
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOnlySpan span, int i)
+ public static ref readonly T DangerousGetLookupReferenceAt(this ReadOnlySpan span, int i)
{
// Check whether the input is in range by first casting both
// operands to uint and then comparing them, as this allows
@@ -141,11 +162,57 @@ public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOn
mask = ~negativeFlag,
offset = (uint)i & mask;
ref T r0 = ref MemoryMarshal.GetReference(span);
- ref T r1 = ref Unsafe.Add(ref r0, (IntPtr)(void*)offset);
+ ref T r1 = ref Unsafe.Add(ref r0, (nint)offset);
return ref r1;
}
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int height, int width)
+ {
+ return new ReadOnlySpan2D(span, height, width);
+ }
+
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The initial offset within .
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The pitch in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int offset, int height, int width, int pitch)
+ {
+ return new ReadOnlySpan2D(span, offset, height, width, pitch);
+ }
+#endif
+
///
/// Gets the index of an element of a given from its reference.
///
@@ -156,34 +223,20 @@ public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOn
/// Thrown if does not belong to .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int IndexOf(this ReadOnlySpan span, in T value)
+ public static int IndexOf(this ReadOnlySpan span, in T value)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T r1 = ref Unsafe.AsRef(value);
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1);
- if (sizeof(IntPtr) == sizeof(long))
- {
- long elementOffset = (long)byteOffset / Unsafe.SizeOf();
-
- if ((ulong)elementOffset >= (ulong)span.Length)
- {
- SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
- }
+ nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf();
- return unchecked((int)elementOffset);
- }
- else
+ if ((nuint)elementOffset >= (uint)span.Length)
{
- int elementOffset = (int)byteOffset / Unsafe.SizeOf();
-
- if ((uint)elementOffset >= (uint)span.Length)
- {
- SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
- }
-
- return elementOffset;
+ SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
+
+ return (int)elementOffset;
}
///
@@ -195,13 +248,13 @@ public static unsafe int IndexOf(this ReadOnlySpan span, in T value)
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int Count(this ReadOnlySpan span, T value)
+ public static int Count(this ReadOnlySpan span, T value)
where T : IEquatable
{
ref T r0 = ref MemoryMarshal.GetReference(span);
- IntPtr length = (IntPtr)(void*)(uint)span.Length;
+ nint length = (nint)(uint)span.Length;
- return SpanHelper.Count(ref r0, length, value);
+ return (int)SpanHelper.Count(ref r0, length, value);
}
///
@@ -321,13 +374,41 @@ public static ReadOnlySpanTokenizer Tokenize(this ReadOnlySpan span, T
/// The Djb2 hash is fully deterministic and with no random components.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int GetDjb2HashCode(this ReadOnlySpan span)
+ public static int GetDjb2HashCode(this ReadOnlySpan span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
- IntPtr length = (IntPtr)(void*)(uint)span.Length;
+ nint length = (nint)(uint)span.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
+
+ ///
+ /// Copies the contents of a given into destination instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The instance to copy items into.
+ ///
+ /// Thrown when the destination is shorter than the source .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo(this ReadOnlySpan span, RefEnumerable destination)
+ {
+ destination.CopyFrom(span);
+ }
+
+ ///
+ /// Attempts to copy the contents of a given into destination instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The instance to copy items into.
+ /// Whether or not the operation was successful.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryCopyTo(this ReadOnlySpan span, RefEnumerable destination)
+ {
+ return destination.TryCopyFrom(span);
+ }
}
}
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
index 52dac209876..eecf0519360 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
@@ -8,6 +8,9 @@
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+#if SPAN_RUNTIME_SUPPORT
+using Microsoft.Toolkit.HighPerformance.Memory;
+#endif
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@@ -40,14 +43,78 @@ public static ref T DangerousGetReference(this Span span)
/// 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 unsafe ref T DangerousGetReferenceAt(this Span span, int i)
+ 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, (IntPtr)(void*)(uint)i);
+ ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
+ ///
+ /// 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, nint i)
+ {
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref T ri = ref Unsafe.Add(ref r0, i);
+
+ return ref ri;
+ }
+
+#if SPAN_RUNTIME_SUPPORT
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span2D AsSpan2D(this Span span, int height, int width)
+ {
+ return new Span2D(span, height, width);
+ }
+
+ ///
+ /// Returns a instance wrapping the underlying data for the given instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The initial offset within .
+ /// The height of the resulting 2D area.
+ /// The width of each row in the resulting 2D area.
+ /// The pitch in the resulting 2D area.
+ /// The resulting instance.
+ ///
+ /// Thrown when one of the input parameters is out of range.
+ ///
+ ///
+ /// Thrown when the requested area is outside of bounds for .
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span2D AsSpan2D(this Span span, int offset, int height, int width, int pitch)
+ {
+ return new Span2D(span, offset, height, width, pitch);
+ }
+#endif
+
///
/// 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.
@@ -102,33 +169,19 @@ public static Span Cast(this Span span)
/// Thrown if does not belong to .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int IndexOf(this Span span, ref T value)
+ public static int IndexOf(this Span span, ref T value)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value);
- if (sizeof(IntPtr) == sizeof(long))
- {
- long elementOffset = (long)byteOffset / Unsafe.SizeOf();
-
- if ((ulong)elementOffset >= (ulong)span.Length)
- {
- ThrowArgumentOutOfRangeExceptionForInvalidReference();
- }
+ nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf();
- return unchecked((int)elementOffset);
- }
- else
+ if ((nuint)elementOffset >= (uint)span.Length)
{
- int elementOffset = (int)byteOffset / Unsafe.SizeOf();
-
- if ((uint)elementOffset >= (uint)span.Length)
- {
- ThrowArgumentOutOfRangeExceptionForInvalidReference();
- }
-
- return elementOffset;
+ ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
+
+ return (int)elementOffset;
}
///
@@ -140,13 +193,13 @@ public static unsafe int IndexOf(this Span span, ref T value)
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int Count(this Span span, T value)
+ public static int Count(this Span span, T value)
where T : IEquatable
{
ref T r0 = ref MemoryMarshal.GetReference(span);
- IntPtr length = (IntPtr)(void*)(uint)span.Length;
+ nint length = (nint)(uint)span.Length;
- return SpanHelper.Count(ref r0, length, value);
+ return (int)SpanHelper.Count(ref r0, length, value);
}
///
@@ -211,15 +264,43 @@ public static SpanTokenizer Tokenize(this Span span, T separator)
/// The Djb2 hash is fully deterministic and with no random components.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int GetDjb2HashCode(this Span span)
+ public static int GetDjb2HashCode(this Span span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
- IntPtr length = (IntPtr)(void*)(uint)span.Length;
+ nint length = (nint)(uint)span.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
+ ///
+ /// Copies the contents of a given into destination instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The instance to copy items into.
+ ///
+ /// Thrown when the destination is shorter than the source .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyTo(this Span span, RefEnumerable destination)
+ {
+ destination.CopyFrom(span);
+ }
+
+ ///
+ /// Attempts to copy the contents of a given into destination instance.
+ ///
+ /// The type of items in the input instance.
+ /// The input instance.
+ /// The instance to copy items into.
+ /// Whether or not the operation was successful.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryCopyTo(this Span span, RefEnumerable destination)
+ {
+ return destination.TryCopyFrom(span);
+ }
+
///
/// Throws an when the given reference is out of range.
///
diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs
index c9cdc114c36..223480d5b06 100644
--- a/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs
+++ b/Microsoft.Toolkit.HighPerformance/Extensions/StringExtensions.cs
@@ -48,7 +48,7 @@ public static ref char DangerousGetReference(this string text)
/// 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 unsafe ref char DangerousGetReferenceAt(this string text, int i)
+ public static ref char DangerousGetReferenceAt(this string text, int i)
{
#if NETCOREAPP3_1
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
@@ -57,7 +57,7 @@ public static unsafe ref char DangerousGetReferenceAt(this string text, int i)
#else
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
#endif
- ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
+ ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
@@ -91,12 +91,12 @@ private sealed class RawStringData
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe int Count(this string text, char c)
+ public static int Count(this string text, char c)
{
ref char r0 = ref text.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)text.Length;
+ nint length = (nint)(uint)text.Length;
- return SpanHelper.Count(ref r0, length, c);
+ return (int)SpanHelper.Count(ref r0, length, c);
}
///
@@ -160,7 +160,7 @@ public static ReadOnlySpanTokenizer Tokenize(this string text, char separa
public static unsafe int GetDjb2HashCode(this string text)
{
ref char r0 = ref text.DangerousGetReference();
- IntPtr length = (IntPtr)(void*)(uint)text.Length;
+ nint length = (nint)(uint)text.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs
index 3c2a43c32a4..c4c651782bb 100644
--- a/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs
+++ b/Microsoft.Toolkit.HighPerformance/Helpers/HashCode{T}.cs
@@ -9,6 +9,11 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
+#if SPAN_RUNTIME_SUPPORT
+using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
+#else
+using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
+#endif
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
@@ -25,14 +30,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// For more info, see .
///
public struct HashCode
-#if SPAN_RUNTIME_SUPPORT
where T : notnull
-#else
- // If we lack the RuntimeHelpers.IsReferenceOrContainsReferences API,
- // we need to constraint the generic type parameter to unmanaged, as we
- // wouldn't otherwise be able to properly validate it at runtime.
- where T : unmanaged
-#endif
{
///
/// Gets a content hash from the input instance using the xxHash32 algorithm.
@@ -57,19 +55,17 @@ public static int Combine(ReadOnlySpan span)
/// The returned hash code is not processed through APIs.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe int CombineValues(ReadOnlySpan span)
+ internal static int CombineValues(ReadOnlySpan span)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
-#if SPAN_RUNTIME_SUPPORT
// If typeof(T) is not unmanaged, iterate over all the items one by one.
// This check is always known in advance either by the JITter or by the AOT
// compiler, so this branch will never actually be executed by the code.
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
- return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
+ return SpanHelper.GetDjb2HashCode(ref r0, (nint)(uint)span.Length);
}
-#endif
// Get the info for the target memory area to process.
// The line below is computing the total byte size for the span,
@@ -79,7 +75,7 @@ internal static unsafe int CombineValues(ReadOnlySpan span)
// process. In that case it will just compute the byte size as a 32 bit
// multiplication with overflow, which is guaranteed never to happen anyway.
ref byte rb = ref Unsafe.As(ref r0);
- IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf());
+ nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf());
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
}
diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs
new file mode 100644
index 00000000000..2844a34fa8b
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RefEnumerableHelper.cs
@@ -0,0 +1,267 @@
+// 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.Runtime.CompilerServices;
+
+namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
+{
+ ///
+ /// Helpers to process sequences of values by reference with a given step.
+ ///
+ internal static class RefEnumerableHelper
+ {
+ ///
+ /// Clears a target memory area.
+ ///
+ /// The type of values to clear.
+ /// A reference to the start of the memory area.
+ /// The number of items in the memory area.
+ /// The number of items between each consecutive target value.
+ public static void Clear(ref T r0, nint length, nint step)
+ {
+ nint offset = 0;
+
+ // Main loop with 8 unrolled iterations
+ while (length >= 8)
+ {
+ Unsafe.Add(ref r0, offset) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+
+ length -= 8;
+ offset += step;
+ }
+
+ if (length >= 4)
+ {
+ Unsafe.Add(ref r0, offset) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+ Unsafe.Add(ref r0, offset += step) = default!;
+
+ length -= 4;
+ offset += step;
+ }
+
+ // Clear the remaining values
+ while (length > 0)
+ {
+ Unsafe.Add(ref r0, offset) = default!;
+
+ length -= 1;
+ offset += step;
+ }
+ }
+
+ ///
+ /// Copies a sequence of discontiguous items from one memory area to another.
+ ///
+ /// The type of items to copy.
+ /// The source reference to copy from.
+ /// The target reference to copy to.
+ /// The total number of items to copy.
+ /// The step between consecutive items in the memory area pointed to by .
+ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
+ {
+ nint
+ sourceOffset = 0,
+ destinationOffset = 0;
+
+ while (length >= 8)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+
+ length -= 8;
+ sourceOffset += sourceStep;
+ destinationOffset += 8;
+ }
+
+ if (length >= 4)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+
+ length -= 4;
+ sourceOffset += sourceStep;
+ destinationOffset += 4;
+ }
+
+ while (length > 0)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+
+ length -= 1;
+ sourceOffset += sourceStep;
+ destinationOffset += 1;
+ }
+ }
+
+ ///
+ /// Copies a sequence of discontiguous items from one memory area to another.
+ ///
+ /// The type of items to copy.
+ /// The source reference to copy from.
+ /// The target reference to copy to.
+ /// The total number of items to copy.
+ /// The step between consecutive items in the memory area pointed to by .
+ /// The step between consecutive items in the memory area pointed to by .
+ public static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep, nint destinationStep)
+ {
+ nint
+ sourceOffset = 0,
+ destinationOffset = 0;
+
+ while (length >= 8)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+
+ length -= 8;
+ sourceOffset += sourceStep;
+ destinationOffset += destinationStep;
+ }
+
+ if (length >= 4)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+ Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
+
+ length -= 4;
+ sourceOffset += sourceStep;
+ destinationOffset += destinationStep;
+ }
+
+ while (length > 0)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+
+ length -= 1;
+ sourceOffset += sourceStep;
+ destinationOffset += destinationStep;
+ }
+ }
+
+ ///
+ /// Copies a sequence of discontiguous items from one memory area to another. This mirrors
+ /// , but refers to instead.
+ ///
+ /// The type of items to copy.
+ /// The source reference to copy from.
+ /// The target reference to copy to.
+ /// The total number of items to copy.
+ /// The step between consecutive items in the memory area pointed to by .
+ public static void CopyFrom(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
+ {
+ nint
+ sourceOffset = 0,
+ destinationOffset = 0;
+
+ while (length >= 8)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 4);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 5);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 6);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 7);
+
+ length -= 8;
+ sourceOffset += 8;
+ destinationOffset += sourceStep;
+ }
+
+ if (length >= 4)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
+ Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
+
+ length -= 4;
+ sourceOffset += 4;
+ destinationOffset += sourceStep;
+ }
+
+ while (length > 0)
+ {
+ Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
+
+ length -= 1;
+ sourceOffset += 1;
+ destinationOffset += sourceStep;
+ }
+ }
+
+ ///
+ /// Fills a target memory area.
+ ///
+ /// The type of values to fill.
+ /// A reference to the start of the memory area.
+ /// The number of items in the memory area.
+ /// The number of items between each consecutive target value.
+ /// The value to assign to every item in the target memory area.
+ public static void Fill(ref T r0, nint length, nint step, T value)
+ {
+ nint offset = 0;
+
+ while (length >= 8)
+ {
+ Unsafe.Add(ref r0, offset) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+
+ length -= 8;
+ offset += step;
+ }
+
+ if (length >= 4)
+ {
+ Unsafe.Add(ref r0, offset) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+ Unsafe.Add(ref r0, offset += step) = value;
+
+ length -= 4;
+ offset += step;
+ }
+
+ while (length > 0)
+ {
+ Unsafe.Add(ref r0, offset) = value;
+
+ length -= 1;
+ offset += step;
+ }
+ }
+ }
+}
diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs
new file mode 100644
index 00000000000..d14bc211193
--- /dev/null
+++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/RuntimeHelpers.cs
@@ -0,0 +1,275 @@
+// 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.
+
+#pragma warning disable SA1512
+
+// The portable implementation in this type is originally from CoreFX.
+// See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs.
+
+using System;
+using System.Diagnostics.Contracts;
+#if !SPAN_RUNTIME_SUPPORT
+using System.Reflection;
+#endif
+using System.Runtime.CompilerServices;
+using Microsoft.Toolkit.HighPerformance.Extensions;
+
+namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
+{
+ ///
+ /// A helper class that act as polyfill for .NET Standard 2.0 and below.
+ ///
+ internal static class RuntimeHelpers
+ {
+ ///
+ /// Gets the length of a given array as a native integer.
+ ///
+ /// The type of values in the array.
+ /// The input instance.
+ /// The total length of as a native integer.
+ ///
+ /// This method is needed because this expression is not inlined correctly if the target array
+ /// is only visible as a non-generic instance, because the C# compiler will
+ /// not be able to emit the opcode instead of calling the right method.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint GetArrayNativeLength(T[] array)
+ {
+#if NETSTANDARD1_4
+ // .NET Standard 1.4 doesn't include the API to get the long length, so
+ // we just cast the length and throw in case the array is larger than
+ // int.MaxValue. There's not much we can do in this specific case.
+ return (nint)(uint)array.Length;
+#else
+ return (nint)array.LongLength;
+#endif
+ }
+
+ ///
+ /// Gets the length of a given array as a native integer.
+ ///
+ /// The input instance.
+ /// The total length of as a native integer.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint GetArrayNativeLength(Array array)
+ {
+#if NETSTANDARD1_4
+ return (nint)(uint)array.Length;
+#else
+ return (nint)array.LongLength;
+#endif
+ }
+
+ ///
+ /// Gets the byte offset to the first element in a SZ array.
+ ///
+ /// The type of values in the array.
+ /// The byte offset to the first element in a SZ array.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IntPtr GetArrayDataByteOffset()
+ {
+ return TypeInfo.ArrayDataByteOffset;
+ }
+
+ ///
+ /// Gets the byte offset to the first element in a 2D array.
+ ///
+ /// The type of values in the array.
+ /// The byte offset to the first element in a 2D array.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IntPtr GetArray2DDataByteOffset()
+ {
+ return TypeInfo.Array2DDataByteOffset;
+ }
+
+ ///
+ /// Gets the byte offset to the first element in a 3D array.
+ ///
+ /// The type of values in the array.
+ /// The byte offset to the first element in a 3D array.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IntPtr GetArray3DDataByteOffset()
+ {
+ return TypeInfo.Array3DDataByteOffset;
+ }
+
+#if !SPAN_RUNTIME_SUPPORT
+ ///
+ /// Gets a byte offset describing a portable pinnable reference. This can either be an
+ /// interior pointer into some object data (described with a valid reference
+ /// and a reference to some of its data), or a raw pointer (described with a
+ /// reference to an , and a reference that is assumed to refer to pinned data).
+ ///
+ /// The type of field being referenced.
+ /// The input hosting the target field.
+ /// A reference to a target field of type within .
+ ///
+ /// The value representing the offset to the target field from the start of the object data
+ /// for the parameter , or the value of the raw pointer passed as a tracked reference.
+ ///
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe IntPtr GetObjectDataOrReferenceByteOffset(object? obj, ref T data)
+ {
+ if (obj is null)
+ {
+ return (IntPtr)Unsafe.AsPointer(ref data);
+ }
+
+ return obj.DangerousGetObjectDataByteOffset(ref data);
+ }
+
+ ///
+ /// Gets a reference from data describing a portable pinnable reference. This can either be an
+ /// interior pointer into some object data (described with a valid reference
+ /// and a byte offset into its data), or a raw pointer (described with a
+ /// reference to an , and a byte offset representing the value of the raw pointer).
+ ///
+ /// The type of reference to retrieve.
+ /// The input hosting the target field.
+ /// The input byte offset for the reference to retrieve.
+ /// A reference matching the given parameters.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ref T GetObjectDataAtOffsetOrPointerReference(object? obj, IntPtr offset)
+ {
+ if (obj is null)
+ {
+ return ref Unsafe.AsRef((void*)offset);
+ }
+
+ return ref obj.DangerousGetObjectDataReferenceAt(offset);
+ }
+
+ ///
+ /// Checks whether or not a given type is a reference type or contains references.
+ ///
+ /// The type to check.
+ /// Whether or not respects the constraint.
+ [Pure]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsReferenceOrContainsReferences()
+ {
+ return TypeInfo.IsReferenceOrContainsReferences;
+ }
+
+ ///
+ /// Implements the logic for .
+ ///
+ /// The current type to check.
+ /// Whether or not is a reference type or contains references.
+ [Pure]
+ private static bool IsReferenceOrContainsReferences(Type type)
+ {
+ // Common case, for primitive types
+ if (type.GetTypeInfo().IsPrimitive)
+ {
+ return false;
+ }
+
+ if (!type.GetTypeInfo().IsValueType)
+ {
+ return true;
+ }
+
+ // Check if the type is Nullable
+ if (Nullable.GetUnderlyingType(type) is Type nullableType)
+ {
+ type = nullableType;
+ }
+
+ if (type.GetTypeInfo().IsEnum)
+ {
+ return false;
+ }
+
+ // Complex struct, recursively inspect all fields
+ foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields)
+ {
+ if (field.IsStatic)
+ {
+ continue;
+ }
+
+ if (IsReferenceOrContainsReferences(field.FieldType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+#endif
+
+ ///
+ /// A private generic class to preload type info for arbitrary runtime types.
+ ///
+ /// The type to load info for.
+ private static class TypeInfo
+ {
+ ///
+ /// The byte offset to the first element in a SZ array.
+ ///
+ public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
+
+ ///
+ /// The byte offset to the first element in a 2D array.
+ ///
+ public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
+
+ ///
+ /// The byte offset to the first element in a 3D array.
+ ///
+ public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
+
+#if !SPAN_RUNTIME_SUPPORT
+ ///
+ /// Indicates whether does not respect the constraint.
+ ///
+ public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
+#endif
+
+ ///
+ /// Computes the value for .
+ ///
+ /// The value of for the current runtime.
+ [Pure]
+ private static IntPtr MeasureArrayDataByteOffset()
+ {
+ var array = new T[1];
+
+ return array.DangerousGetObjectDataByteOffset(ref array[0]);
+ }
+
+ ///
+ /// Computes the value for .
+ ///
+ /// The value of for the current runtime.
+ [Pure]
+ private static IntPtr MeasureArray2DDataByteOffset()
+ {
+ var array = new T[1, 1];
+
+ return array.DangerousGetObjectDataByteOffset(ref array[0, 0]);
+ }
+
+ ///
+ /// Computes the value for .
+ ///
+ /// The value of for the current runtime.
+ [Pure]
+ private static IntPtr MeasureArray3DDataByteOffset()
+ {
+ var array = new T[1, 1, 1];
+
+ return array.DangerousGetObjectDataByteOffset(ref array[0, 0, 0]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs
index 8e1288bda9e..408b3073cf1 100644
--- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs
+++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Count.cs
@@ -25,7 +25,7 @@ internal static partial class SpanHelper
/// The number of occurrences of in the search space
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int Count(ref T r0, IntPtr length, T value)
+ public static nint Count(ref T r0, nint length, T value)
where T : IEquatable
{
if (!Vector.IsHardwareAccelerated)
@@ -41,7 +41,7 @@ public static int Count(ref T r0, IntPtr length, T value)
ref sbyte r1 = ref Unsafe.As(ref r0);
sbyte target = Unsafe.As(ref value);
- return CountSimd(ref r1, length, target, (IntPtr)sbyte.MaxValue);
+ return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(char) ||
@@ -51,7 +51,7 @@ public static int Count(ref T r0, IntPtr length, T value)
ref short r1 = ref Unsafe.As(ref r0);
short target = Unsafe.As(ref value);
- return CountSimd(ref r1, length, target, (IntPtr)short.MaxValue);
+ return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(int) ||
@@ -60,7 +60,7 @@ public static int Count(ref T r0, IntPtr length, T value)
ref int r1 = ref Unsafe.As(ref r0);
int target = Unsafe.As(ref value);
- return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
+ return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(long) ||
@@ -69,7 +69,7 @@ public static int Count(ref T r0, IntPtr length, T value)
ref long r1 = ref Unsafe.As(ref r0);
long target = Unsafe.As(ref value);
- return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
+ return CountSimd(ref r1, length, target);
}
return CountSequential(ref r0, length, value);
@@ -82,44 +82,44 @@ public static int Count(ref T r0, IntPtr length, T value)
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
- private static unsafe int CountSequential(ref T r0, IntPtr length, T value)
+ private static nint CountSequential(ref T r0, nint length, T value)
where T : IEquatable
{
- int result = 0;
-
- IntPtr offset = default;
+ nint
+ result = 0,
+ offset = 0;
// Main loop with 8 unrolled iterations
- while ((byte*)length >= (byte*)8)
+ while (length >= 8)
{
- result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte();
length -= 8;
offset += 8;
}
- if ((byte*)length >= (byte*)4)
+ if (length >= 4)
{
- result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
- result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
+ result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
length -= 4;
offset += 4;
}
// Iterate over the remaining values and count those that match
- while ((byte*)length > (byte*)0)
+ while (length > 0)
{
- result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
+ result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
length -= 1;
offset += 1;
@@ -135,15 +135,15 @@ private static unsafe int CountSequential