Merge branch 'master' into lazyLoadingThreshold

This commit is contained in:
h82258652 2020-11-06 08:47:41 +08:00 коммит произвёл GitHub
Родитель 7ae5a0c830 fdee56339c
Коммит fb40d85cc1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
87 изменённых файлов: 11874 добавлений и 1450 удалений

Просмотреть файл

@ -24,7 +24,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// the arrays in use are rented from the shared <see cref="ArrayPool{T}"/> instance,
/// and that <see cref="ArrayPoolBufferWriter{T}"/> is also available on .NET Standard 2.0.
/// </remarks>
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
{

Просмотреть файл

@ -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
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap arbitrary <see cref="object"/> instances.
/// </summary>
/// <typeparam name="T">The type of elements in the target memory area.</typeparam>
internal sealed class RawObjectMemoryManager<T> : MemoryManager<T>
{
/// <summary>
/// The target <see cref="object"/> instance.
/// </summary>
private readonly object instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The length of the target memory area.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="RawObjectMemoryManager{T}"/> class.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The starting offset within <paramref name="instance"/>.</param>
/// <param name="length">The usable length within <paramref name="instance"/>.</param>
public RawObjectMemoryManager(object instance, IntPtr offset, int length)
{
this.instance = instance;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<T> GetSpan()
{
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return MemoryMarshal.CreateSpan(ref r0, this.length);
}
/// <inheritdoc/>
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<T> 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<T>(this.offset);
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex);
void* p = Unsafe.AsPointer(ref r1);
return new MemoryHandle(p, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input index for <see cref="Pin"/> is not valid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range");
}
}
}
#endif

Просмотреть файл

@ -7,6 +7,7 @@ using System.Buffers;
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 <see cref="Memory{T}"/>), to ensure the data is written directly
/// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity.
/// </remarks>
[DebuggerTypeProxy(typeof(MemoryBufferWriter<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryBufferWriter<T> : IBuffer<T>
{

Просмотреть файл

@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and a fast <see cref="Span{T}"/> accessor.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryOwnerDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryOwner<T> : IMemoryOwner<T>
{

Просмотреть файл

@ -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.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly ref struct SpanOwner<T>
{

Просмотреть файл

@ -536,7 +536,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
(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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="value">The new <see cref="string"/> instance to store.</param>
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
[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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="mapIndex">The index of the target map node to remove.</param>
/// <remarks>The input <see cref="string"/> instance needs to already exist in the map.</remarks>
[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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// </summary>
/// <param name="heapIndex">The index of the target heap node to update.</param>
[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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
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 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// a given number of nodes, those are all contiguous from the start of the array.
/// </summary>
[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;
}
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A debug proxy used to display items for the <see cref="ArrayPoolBufferWriter{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
internal sealed class ArrayPoolBufferWriterDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriterDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
{
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A debug proxy used to display items for the <see cref="MemoryBufferWriter{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
internal sealed class MemoryBufferWriterDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryBufferWriterDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
public MemoryBufferWriterDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
{
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A debug proxy used to display items in a 1D layout.
/// </summary>
/// <typeparam name="T">The type of items to display.</typeparam>
internal sealed class MemoryDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
public MemoryDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
{
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
public MemoryDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
{
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
public MemoryDebugView(MemoryOwner<T>? memoryOwner)
{
this.Items = memoryOwner?.Span.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
public MemoryDebugView(SpanOwner<T> spanOwner)
{
this.Items = spanOwner.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public T[]? Items { get; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A debug proxy used to display items for the <see cref="MemoryOwner{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="MemoryOwner{T}"/> instances.</typeparam>
internal sealed class MemoryOwnerDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwnerDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
public MemoryOwnerDebugView(MemoryOwner<T>? memoryOwner)
{
this.Items = memoryOwner?.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A debug proxy used to display items for the <see cref="SpanOwner{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="SpanOwner{T}"/> instances.</typeparam>
internal sealed class SpanOwnerDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="SpanOwnerDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
public SpanOwnerDebugView(SpanOwner<T> spanOwner)
{
this.Items = spanOwner.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates a column in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct Array2DColumnEnumerable<T>
{
/// <summary>
/// The source 2D <typeparamref name="T"/> array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target column to iterate within <see cref="array"/>.
/// </summary>
private readonly int column;
/// <summary>
/// Initializes a new instance of the <see cref="Array2DColumnEnumerable{T}"/> struct.
/// </summary>
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Array2DColumnEnumerable(T[,] array, int column)
{
this.array = array;
this.column = column;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.array, this.column);
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target column.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target column.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[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;
}
/// <summary>
/// An enumerator for a source 2D array instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
/// </summary>
/// <remarks>
/// In runtimes where we have support for the <see cref="Span{T}"/> 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 <see cref="width"/> to the offset at each
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
/// 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.
/// </remarks>
private readonly Span<T> span;
/// <summary>
/// The width of the target 2D array.
/// </summary>
private readonly int width;
/// <summary>
/// The current absolute offset within <see cref="span"/>.
/// </summary>
private int offset;
#else
/// <summary>
/// The source 2D array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target column to iterate within <see cref="array"/>.
/// </summary>
private readonly int column;
/// <summary>
/// The height of a column in <see cref="array"/>.
/// </summary>
private readonly int height;
/// <summary>
/// The current row.
/// </summary>
private int row;
#endif
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="array">The source 2D array instance.</param>
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
[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
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[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;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
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
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
{
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
}
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct Array2DRowEnumerable<T>
{
/// <summary>
/// The source 2D <typeparamref name="T"/> array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target row to iterate within <see cref="array"/>.
/// </summary>
private readonly int row;
/// <summary>
/// Initializes a new instance of the <see cref="Array2DRowEnumerable{T}"/> struct.
/// </summary>
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Array2DRowEnumerable(T[,] array, int row)
{
this.array = array;
this.row = row;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.array, this.row);
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target row.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[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;
}
/// <summary>
/// An enumerator for a source 2D array instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source 2D array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target row to iterate within <see cref="array"/>.
/// </summary>
private readonly int row;
/// <summary>
/// The width of a row in <see cref="array"/>.
/// </summary>
private readonly int width;
/// <summary>
/// The current column.
/// </summary>
private int column;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="array">The source 2D array instance.</param>
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
[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;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int column = this.column + 1;
if (column < this.width)
{
this.column = column;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
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];
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
{
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
}
}
}
#endif

Просмотреть файл

@ -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
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates readonly items from arbitrary memory locations.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
public readonly ref struct ReadOnlyRefEnumerable<T>
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
private readonly ReadOnlySpan<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The total available length for the sequence.
/// </summary>
private readonly int length;
#endif
/// <summary>
/// The distance between items in the sequence to enumerate.
/// </summary>
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
private readonly int step;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyRefEnumerable(ReadOnlySpan<T> span, int step)
{
this.span = span;
this.step = step;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="reference">A reference to the first item of the sequence.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[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
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[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
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[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
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyRefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(RefEnumerable<T> 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<T>(this.instance, this.offset);
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(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);
}
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyRefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(RefEnumerable<T> 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;
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(Span<T> 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<T>(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);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Span<T> 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;
}
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
[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>();
}
T[] array = new T[length];
CopyTo(array);
return array;
}
/// <summary>
/// Implicitly converts a <see cref="RefEnumerable{T}"/> instance into a <see cref="ReadOnlyRefEnumerable{T}"/> one.
/// </summary>
/// <param name="enumerable">The input <see cref="RefEnumerable{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumerable)
{
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(enumerable.Span, enumerable.Step);
#else
return new ReadOnlyRefEnumerable<T>(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
#endif
}
/// <summary>
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
private readonly ReadOnlySpan<T> span;
#else
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
private readonly object? instance;
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
private readonly IntPtr offset;
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
private readonly int length;
#endif
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
private readonly int step;
/// <summary>
/// The current position in the sequence.
/// </summary>
private int position;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance with the info on the items to traverse.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(ReadOnlySpan<T> span, int step)
{
this.span = span;
this.step = step;
this.position = -1;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[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
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
#if SPAN_RUNTIME_SUPPORT
return ++this.position < this.span.Length;
#else
return ++this.position < this.length;
#endif
}
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
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<T>(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;
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>
private static void ThrowArgumentExceptionForDestinationTooShort()
{
throw new ArgumentException("The target span is too short to copy all the current items to");
}
}
}

Просмотреть файл

@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
[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;
}
/// <summary>
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
#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<T> about this
return new Item(ref ri, this.index);
@ -139,7 +130,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
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

Просмотреть файл

@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator)
{
this.span = span;

Просмотреть файл

@ -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
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates items from arbitrary memory locations.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
public readonly ref struct RefEnumerable<T>
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>The <see cref="Span{T}.Length"/> field maps to the total available length.</remarks>
internal readonly Span<T> Span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
internal readonly object? Instance;
/// <summary>
/// The initial offset within <see cref="Instance"/>.
/// </summary>
internal readonly IntPtr Offset;
/// <summary>
/// The total available length for the sequence.
/// </summary>
internal readonly int Length;
#endif
/// <summary>
/// The distance between items in the sequence to enumerate.
/// </summary>
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
internal readonly int Step;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
/// </summary>
/// <param name="reference">A reference to the first item of the sequence.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal RefEnumerable(ref T reference, int length, int step)
{
Span = MemoryMarshal.CreateSpan(ref reference, length);
Step = step;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
{
Instance = instance;
Offset = offset;
Length = length;
Step = step;
}
#endif
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[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
}
/// <summary>
/// Clears the contents of the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
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<T>(this.Instance, this.Offset);
int length = this.Length;
#endif
RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step);
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(RefEnumerable<T> 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<T>(this.Instance, this.Offset);
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(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);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(RefEnumerable<T> 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;
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(Span<T> 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<T>(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);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Span<T> 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;
}
/// <summary>
/// Copies the contents of a source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when the current <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/> instance.
/// </exception>
internal void CopyFrom(ReadOnlySpan<T> 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<T>(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);
}
/// <summary>
/// Attempts to copy the source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyFrom(ReadOnlySpan<T> 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;
}
/// <summary>
/// Fills the elements of this <see cref="RefEnumerable{T}"/> with a specified value.
/// </summary>
/// <param name="value">The value to assign to each element of the <see cref="RefEnumerable{T}"/> instance.</param>
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<T>(this.Instance, this.Offset);
int length = this.Length;
#endif
RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value);
}
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target row.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[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>();
}
T[] array = new T[length];
CopyTo(array);
return array;
}
/// <summary>
/// A custom enumerator type to traverse items within a <see cref="RefEnumerable{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <inheritdoc cref="RefEnumerable{T}.Span"/>
private readonly Span<T> span;
#else
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
private readonly object? instance;
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
private readonly IntPtr offset;
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
private readonly int length;
#endif
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
private readonly int step;
/// <summary>
/// The current position in the sequence.
/// </summary>
private int position;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/> instance with the info on the items to traverse.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(Span<T> span, int step)
{
this.span = span;
this.step = step;
this.position = -1;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[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
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
#if SPAN_RUNTIME_SUPPORT
return ++this.position < this.span.Length;
#else
return ++this.position < this.length;
#endif
}
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
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<T>(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;
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>
private static void ThrowArgumentExceptionForDestinationTooShort()
{
throw new ArgumentException("The target span is too short to copy all the current items to");
}
}
}

Просмотреть файл

@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
[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;
}
/// <summary>
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
#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<T> types), we can save 4 bytes by piggybacking the
@ -144,7 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
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

Просмотреть файл

@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanTokenizer(Span<T> span, T separator)
{
this.span = span;

Просмотреть файл

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
#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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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<T>();
unsafe
{
return ref Unsafe.AsRef<T>(null);
}
#pragma warning restore SA1131
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
#endif
}
@ -62,21 +52,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array);
ref T r0 = ref Unsafe.As<byte, T>(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<T>();
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref Unsafe.AsRef<T>(null);
return ref ri;
#endif
}
@ -111,13 +100,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count<T>(this T[] array, T value)
public static int Count<T>(this T[] array, T value)
where T : IEquatable<T>
{
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);
return SpanHelper.Count(ref r0, length, value);
if ((nuint)count > int.MaxValue)
{
ThrowOverflowException();
}
return (int)count;
}
/// <summary>
@ -182,13 +178,34 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetDjb2HashCode<T>(this T[] array)
public static int GetDjb2HashCode<T>(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);
}
/// <summary>
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
/// </summary>
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsCovariant<T>(this T[] array)
{
return default(T) is null && array.GetType() != typeof(T[]);
}
/// <summary>
/// Throws an <see cref="OverflowException"/> when the "column" parameter is invalid.
/// </summary>
public static void ThrowOverflowException()
{
throw new OverflowException();
}
}
}

Просмотреть файл

@ -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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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<T>();
unsafe
{
return ref Unsafe.AsRef<T>(null);
}
#pragma warning restore SA1131
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
#endif
}
@ -66,23 +60,23 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(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<byte, T>(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<T>();
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
ref T ri = ref Unsafe.Add(ref r0, index);
return ref Unsafe.AsRef<T>(null);
return ref ri;
#endif
}
@ -112,95 +106,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
#endif
/// <summary>
/// Fills an area in a given 2D <typeparamref name="T"/> 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.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="value">The <typeparamref name="T"/> value to fill the target area with.</param>
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
/// <param name="width">The positive width of area to fill.</param>
/// <param name="height">The positive height of area to fill.</param>
public static void Fill<T>(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<T>.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<T> extension on the array.
Unsafe.Add(ref r0, j) = value;
}
#endif
}
}
/// <summary>
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// Returns a <see cref="RefEnumerable{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to retrieve (0-based index).</param>
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <returns>A <see cref="RefEnumerable{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static
#if SPAN_RUNTIME_SUPPORT
Span<T>
#else
// .NET Standard 2.0 lacks MemoryMarshal.CreateSpan<T>(ref T, int),
// which is necessary to create arbitrary Span<T>-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<T> 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<T>
#endif
GetRow<T>(this T[,] array, int row)
public static RefEnumerable<T> GetRow<T>(this T[,] array, int row)
{
if ((uint)row >= (uint)array.GetLength(0))
if (array.IsCovariant())
{
throw new ArgumentOutOfRangeException(nameof(row));
ThrowArrayTypeMismatchException();
}
int height = array.GetLength(0);
if ((uint)row >= (uint)height)
{
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<T>(ref r0, width, 1);
#else
return new Array2DRowEnumerable<T>(array, row);
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
return new RefEnumerable<T>(array, offset, width, 1);
#endif
}
/// <summary>
/// Returns an enumerable that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
/// Returns a <see cref="RefEnumerable{T}"/> that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// int[,] matrix =
@ -221,15 +166,196 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="column">The target column to retrieve (0-based index).</param>
/// <returns>A wrapper type that will handle the column enumeration for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="Array2DColumnEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Array2DColumnEnumerable<T> GetColumn<T>(this T[,] array, int column)
public static RefEnumerable<T> GetColumn<T>(this T[,] array, int column)
{
return new Array2DColumnEnumerable<T>(array, 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<T>(ref r0, height, width);
#else
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
return new RefEnumerable<T>(array, offset, height, width);
#endif
}
/// <summary>
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,]? array)
{
return new Span2D<T>(array);
}
/// <summary>
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,]? array, int row, int column, int height, int width)
{
return new Span2D<T>(array, row, column, height, width);
}
/// <summary>
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this T[,]? array)
{
return new Memory2D<T>(array);
}
/// <summary>
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this T[,]? array, int row, int column, int height, int width)
{
return new Memory2D<T>(array, row, column, height, width);
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to retrieve (0-based index).</param>
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(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));
}
/// <summary>
/// Returns a <see cref="Memory{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to retrieve (0-based index).</param>
/// <returns>A <see cref="Memory{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(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<T>(array, offset, array.GetLength(1)).Memory;
}
/// <summary>
/// Creates a new <see cref="Memory{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(this T[,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
int length = array.Length;
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
}
/// <summary>
/// Creates a new <see cref="Span{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
@ -238,26 +364,21 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[,] array)
public static Span<T> AsSpan<T>(this T[,]? array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(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<IntPtr, uint>(ref arrayData.Length));
ref T r0 = ref Unsafe.As<byte, T>(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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
where T : IEquatable<T>
{
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);
return SpanHelper.Count(ref r0, length, value);
if ((nuint)count > int.MaxValue)
{
ThrowOverflowException();
}
return (int)count;
}
/// <summary>
@ -294,9 +422,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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);
}
/// <summary>
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
/// </summary>
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsCovariant<T>(this T[,] array)
{
return default(T) is null && array.GetType() != typeof(T[,]);
}
/// <summary>
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
/// </summary>
private static void ThrowArrayTypeMismatchException()
{
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForRow()
{
throw new ArgumentOutOfRangeException("row");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForColumn()
{
throw new ArgumentOutOfRangeException("column");
}
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// Helpers for working with the <see cref="Array"/> type.
/// </summary>
public static partial class ArrayExtensions
{
/// <summary>
/// Returns a reference to the first element within a given 3D <typeparamref name="T"/> array, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <returns>A reference to the first element within <paramref name="array"/>, or the location it would have used, if <paramref name="array"/> is empty.</returns>
/// <remarks>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.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReference<T>(this T[,,] array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray3DData>(array);
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
#else
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
#endif
}
/// <summary>
/// Returns a reference to an element at a specified coordinate within a given 3D <typeparamref name="T"/> array, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <param name="i">The depth index of the element to retrieve within <paramref name="array"/>.</param>
/// <param name="j">The vertical index of the element to retrieve within <paramref name="array"/>.</param>
/// <param name="k">The horizontal index of the element to retrieve within <paramref name="array"/>.</param>
/// <returns>A reference to the element within <paramref name="array"/> at the coordinate specified by <paramref name="i"/> and <paramref name="j"/>.</returns>
/// <remarks>
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/>
/// and <paramref name="j"/> 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.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray3DData>(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<byte, T>(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<T>();
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(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
/// <summary>
/// Creates a new <see cref="Memory{T}"/> over an input 3D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(this T[,,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
int length = array.Length;
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
}
/// <summary>
/// Creates a new <see cref="Span{T}"/> over an input 3D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(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);
}
/// <summary>
/// Creates a new instance of the <see cref="Span{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Span{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(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);
}
/// <summary>
/// Creates a new instance of the <see cref="Memory{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Memory{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(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<T>(array, offset, length).Memory;
}
#endif
/// <summary>
/// Creates a new instance of the <see cref="Span2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Span2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth)
{
return new Span2D<T>(array, depth);
}
/// <summary>
/// Creates a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Memory2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this T[,,] array, int depth)
{
return new Memory2D<T>(array, depth);
}
/// <summary>
/// Counts the number of occurrences of a given value into a target 3D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this T[,,] array, T value)
where T : IEquatable<T>
{
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;
}
/// <summary>
/// Gets a content hash from the input 3D <typeparamref name="T"/> array instance using the Djb2 algorithm.
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
/// </summary>
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <returns>The Djb2 value for the input 3D <typeparamref name="T"/> array instance.</returns>
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode<T>(this T[,,] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
nint length = RuntimeHelpers.GetArrayNativeLength(array);
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
/// <summary>
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
/// </summary>
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsCovariant<T>(this T[,,] array)
{
return default(T) is null && array.GetType() != typeof(T[,,]);
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForDepth()
{
throw new ArgumentOutOfRangeException("depth");
}
}
}

Просмотреть файл

@ -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
/// </summary>
public static class BoolExtensions
{
/// <summary>
/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
/// </summary>
/// <param name="flag">The input value to convert.</param>
/// <returns>1 if <paramref name="flag"/> is <see langword="true"/>, 0 otherwise.</returns>
/// <remarks>This method does not contain branching instructions.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ToByte(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);
}
/// <summary>
/// Converts the given <see cref="bool"/> value into an <see cref="int"/>.
/// </summary>
@ -20,6 +34,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method does not contain branching instructions.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use ToByte instead.")]
public static int ToInt(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);

Просмотреть файл

@ -23,12 +23,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
#if SPAN_RUNTIME_SUPPORT
where T : notnull
#else
// Same type constraints as HashCode<T>, see comments there
where T : unmanaged
#endif
{
int hash = HashCode<T>.CombineValues(span);

Просмотреть файл

@ -6,6 +6,9 @@ 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
@ -15,6 +18,52 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class MemoryExtensions
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width)
{
return new Memory2D<T>(memory, height, width);
}
/// <summary>
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int offset, int height, int width, int pitch)
{
return new Memory2D<T>(memory, offset, height, width, pitch);
}
#endif
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>

Просмотреть файл

@ -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
/// </summary>
public static class ReadOnlyMemoryExtensions
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width)
{
return new ReadOnlyMemory2D<T>(memory, height, width);
}
/// <summary>
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
{
return new ReadOnlyMemory2D<T>(memory, offset, height, width, pitch);
}
#endif
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
/// </summary>
@ -27,6 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </remarks>
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
{
return MemoryStream.Create(memory, true);

Просмотреть файл

@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> 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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
// 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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
// 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;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> 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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> 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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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
/// <summary>
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int height, int width)
{
return new ReadOnlySpan2D<T>(span, height, width);
}
/// <summary>
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int offset, int height, int width, int pitch)
{
return new ReadOnlySpan2D<T>(span, offset, height, width, pitch);
}
#endif
/// <summary>
/// Gets the index of an element of a given <see cref="ReadOnlySpan{T}"/> from its reference.
/// </summary>
@ -156,34 +223,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
public static int IndexOf<T>(this ReadOnlySpan<T> 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))
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
if ((nuint)elementOffset >= (uint)span.Length)
{
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
if ((ulong)elementOffset >= (ulong)span.Length)
{
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return unchecked((int)elementOffset);
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
else
{
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
if ((uint)elementOffset >= (uint)span.Length)
{
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return elementOffset;
}
return (int)elementOffset;
}
/// <summary>
@ -195,13 +248,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
public static int Count<T>(this ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
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);
}
/// <summary>
@ -321,13 +374,41 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> 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);
}
/// <summary>
/// Copies the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
/// <exception cref="ArgumentException">
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/>.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
{
destination.CopyFrom(span);
}
/// <summary>
/// Attempts to copy the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
/// <returns>Whether or not the operation was successful.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryCopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
{
return destination.TryCopyFrom(span);
}
}
}

Просмотреть файл

@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
public static ref T DangerousGetReferenceAt<T>(this Span<T> 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;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <see cref="Span{T}"/>, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this Span<T> 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
/// <summary>
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int height, int width)
{
return new Span2D<T>(span, height, width);
}
/// <summary>
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int offset, int height, int width, int pitch)
{
return new Span2D<T>(span, offset, height, width, pitch);
}
#endif
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> 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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
public static int IndexOf<T>(this Span<T> span, ref T value)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value);
if (sizeof(IntPtr) == sizeof(long))
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
if ((nuint)elementOffset >= (uint)span.Length)
{
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
if ((ulong)elementOffset >= (ulong)span.Length)
{
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return unchecked((int)elementOffset);
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
else
{
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
if ((uint)elementOffset >= (uint)span.Length)
{
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return elementOffset;
}
return (int)elementOffset;
}
/// <summary>
@ -140,13 +193,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count<T>(this Span<T> span, T value)
public static int Count<T>(this Span<T> span, T value)
where T : IEquatable<T>
{
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);
}
/// <summary>
@ -211,15 +264,43 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
public static int GetDjb2HashCode<T>(this Span<T> 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);
}
/// <summary>
/// Copies the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
/// <exception cref="ArgumentException">
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="Span{T}"/>.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
{
destination.CopyFrom(span);
}
/// <summary>
/// Attempts to copy the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
/// <returns>Whether or not the operation was successful.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryCopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
{
return destination.TryCopyFrom(span);
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
/// </summary>

Просмотреть файл

@ -48,7 +48,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
#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 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
[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);
}
/// <summary>
@ -160,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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);
}

Просмотреть файл

@ -9,6 +9,11 @@ using System.Diagnostics.Contracts;
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 <see href="https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks"/>.
/// </remarks>
public struct HashCode<T>
#if SPAN_RUNTIME_SUPPORT
where T : notnull
#else
// If we lack the RuntimeHelpers.IsReferenceOrContainsReferences<T> 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
{
/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the xxHash32 algorithm.
@ -57,19 +55,17 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
internal static int CombineValues(ReadOnlySpan<T> 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<T>())
{
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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
// 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<T, byte>(ref r0);
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
}

Просмотреть файл

@ -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
{
/// <summary>
/// Helpers to process sequences of values by reference with a given step.
/// </summary>
internal static class RefEnumerableHelper
{
/// <summary>
/// Clears a target memory area.
/// </summary>
/// <typeparam name="T">The type of values to clear.</typeparam>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
/// <param name="length">The number of items in the memory area.</param>
/// <param name="step">The number of items between each consecutive target value.</param>
public static void Clear<T>(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;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
public static void CopyTo<T>(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;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
/// <param name="destinationStep">The step between consecutive items in the memory area pointed to by <paramref name="destinationRef"/>.</param>
public static void CopyTo<T>(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;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another. This mirrors
/// <see cref="CopyTo{T}(ref T,ref T,nint,nint)"/>, but <paramref name="sourceStep"/> refers to <paramref name="destinationRef"/> instead.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
public static void CopyFrom<T>(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;
}
}
/// <summary>
/// Fills a target memory area.
/// </summary>
/// <typeparam name="T">The type of values to fill.</typeparam>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
/// <param name="length">The number of items in the memory area.</param>
/// <param name="step">The number of items between each consecutive target value.</param>
/// <param name="value">The value to assign to every item in the target memory area.</param>
public static void Fill<T>(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;
}
}
}
}

Просмотреть файл

@ -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
{
/// <summary>
/// A helper class that act as polyfill for .NET Standard 2.0 and below.
/// </summary>
internal static class RuntimeHelpers
{
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
/// <remarks>
/// This method is needed because this expression is not inlined correctly if the target array
/// is only visible as a non-generic <see cref="Array"/> instance, because the C# compiler will
/// not be able to emit the <see langword="ldlen"/> opcode instead of calling the right method.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength<T>(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
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength(Array array)
{
#if NETSTANDARD1_4
return (nint)(uint)array.Length;
#else
return (nint)array.LongLength;
#endif
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a SZ array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArrayDataByteOffset<T>()
{
return TypeInfo<T>.ArrayDataByteOffset;
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 2D array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray2DDataByteOffset<T>()
{
return TypeInfo<T>.Array2DDataByteOffset;
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 3D array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray3DDataByteOffset<T>()
{
return TypeInfo<T>.Array3DDataByteOffset;
}
#if !SPAN_RUNTIME_SUPPORT
/// <summary>
/// Gets a byte offset describing a portable pinnable reference. This can either be an
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
/// and a reference to some of its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a reference that is assumed to refer to pinned data).
/// </summary>
/// <typeparam name="T">The type of field being referenced.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
/// <returns>
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
/// for the parameter <paramref name="obj"/>, or the value of the raw pointer passed as a tracked reference.
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe IntPtr GetObjectDataOrReferenceByteOffset<T>(object? obj, ref T data)
{
if (obj is null)
{
return (IntPtr)Unsafe.AsPointer(ref data);
}
return obj.DangerousGetObjectDataByteOffset(ref data);
}
/// <summary>
/// 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 <see cref="object"/> reference
/// and a byte offset into its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a byte offset representing the value of the raw pointer).
/// </summary>
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
/// <returns>A <typeparamref name="T"/> reference matching the given parameters.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T GetObjectDataAtOffsetOrPointerReference<T>(object? obj, IntPtr offset)
{
if (obj is null)
{
return ref Unsafe.AsRef<T>((void*)offset);
}
return ref obj.DangerousGetObjectDataReferenceAt<T>(offset);
}
/// <summary>
/// Checks whether or not a given type is a reference type or contains references.
/// </summary>
/// <typeparam name="T">The type to check.</typeparam>
/// <returns>Whether or not <typeparamref name="T"/> respects the <see langword="unmanaged"/> constraint.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsReferenceOrContainsReferences<T>()
{
return TypeInfo<T>.IsReferenceOrContainsReferences;
}
/// <summary>
/// Implements the logic for <see cref="IsReferenceOrContainsReferences{T}"/>.
/// </summary>
/// <param name="type">The current type to check.</param>
/// <returns>Whether or not <paramref name="type"/> is a reference type or contains references.</returns>
[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<T>
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
/// <summary>
/// A private generic class to preload type info for arbitrary runtime types.
/// </summary>
/// <typeparam name="T">The type to load info for.</typeparam>
private static class TypeInfo<T>
{
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
#if !SPAN_RUNTIME_SUPPORT
/// <summary>
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
/// </summary>
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
#endif
/// <summary>
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArrayDataByteOffset()
{
var array = new T[1];
return array.DangerousGetObjectDataByteOffset(ref array[0]);
}
/// <summary>
/// Computes the value for <see cref="Array2DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array2DDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArray2DDataByteOffset()
{
var array = new T[1, 1];
return array.DangerousGetObjectDataByteOffset(ref array[0, 0]);
}
/// <summary>
/// Computes the value for <see cref="Array3DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array3DDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArray3DDataByteOffset()
{
var array = new T[1, 1, 1];
return array.DangerousGetObjectDataByteOffset(ref array[0, 0, 0]);
}
}
}
}

Просмотреть файл

@ -25,7 +25,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(ref T r0, IntPtr length, T value)
public static nint Count<T>(ref T r0, nint length, T value)
where T : IEquatable<T>
{
if (!Vector.IsHardwareAccelerated)
@ -41,7 +41,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
sbyte target = Unsafe.As<T, sbyte>(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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref short r1 = ref Unsafe.As<T, short>(ref r0);
short target = Unsafe.As<T, short>(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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref int r1 = ref Unsafe.As<T, int>(ref r0);
int target = Unsafe.As<T, int>(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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref long r1 = ref Unsafe.As<T, long>(ref r0);
long target = Unsafe.As<T, long>(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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
private static unsafe int CountSequential<T>(ref T r0, IntPtr length, T value)
private static nint CountSequential<T>(ref T r0, nint length, T value)
where T : IEquatable<T>
{
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 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
private static unsafe int CountSimd<T>(ref T r0, IntPtr length, T value, IntPtr max)
private static nint CountSimd<T>(ref T r0, nint length, T value)
where T : unmanaged, IEquatable<T>
{
int result = 0;
IntPtr offset = default;
nint
result = 0,
offset = 0;
// Skip the initialization overhead if there are not enough items
if ((byte*)length >= (byte*)Vector<T>.Count)
if (length >= Vector<T>.Count)
{
var vc = new Vector<T>(value);
@ -154,13 +154,14 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// to sum the partial results. We also backup the current offset to
// be able to track how many items have been processed, which lets
// us avoid updating a third counter (length) in the loop body.
IntPtr
chunkLength = Min(length, max),
nint
max = GetUpperBound<T>(),
chunkLength = length <= max ? length : max,
initialOffset = offset;
var partials = Vector<T>.Zero;
while ((byte*)chunkLength >= (byte*)Vector<T>.Count)
while (chunkLength >= Vector<T>.Count)
{
ref T ri = ref Unsafe.Add(ref r0, offset);
@ -181,27 +182,26 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += Vector<T>.Count;
}
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
length = Subtract(length, Subtract(offset, initialOffset));
result += CastToNativeInt(Vector.Dot(partials, Vector<T>.One));
length -= offset - initialOffset;
}
while ((byte*)length >= (byte*)Vector<T>.Count);
while (length >= Vector<T>.Count);
}
// Optional 8 unrolled iterations. This is only done when a single SIMD
// register can contain over 8 values of the current type, as otherwise
// there could never be enough items left after the vectorized path
if (Vector<T>.Count > 8 &&
(byte*)length >= (byte*)8)
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;
@ -209,21 +209,21 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// Optional 4 unrolled iterations
if (Vector<T>.Count > 4 &&
(byte*)length >= (byte*)4)
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;
@ -233,73 +233,88 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
/// <summary>
/// Returns the minimum between two <see cref="IntPtr"/> values.
/// Gets the upper bound for partial sums with a given <typeparamref name="T"/> parameter.
/// </summary>
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
/// <param name="b">The second <see cref="IntPtr"/> value</param>
/// <returns>The minimum between <paramref name="a"/> and <paramref name="b"/>.</returns>
/// <typeparam name="T">The type argument currently in use.</typeparam>
/// <returns>The native <see cref="int"/> value representing the upper bound.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe IntPtr Min(IntPtr a, IntPtr b)
private static unsafe nint GetUpperBound<T>()
where T : unmanaged
{
if (sizeof(IntPtr) == 4)
if (typeof(T) == typeof(byte) ||
typeof(T) == typeof(sbyte) ||
typeof(T) == typeof(bool))
{
return (IntPtr)Math.Min((int)a, (int)b);
return sbyte.MaxValue;
}
return (IntPtr)Math.Min((long)a, (long)b);
if (typeof(T) == typeof(char) ||
typeof(T) == typeof(ushort) ||
typeof(T) == typeof(short))
{
return short.MaxValue;
}
if (typeof(T) == typeof(int) ||
typeof(T) == typeof(uint))
{
return int.MaxValue;
}
if (typeof(T) == typeof(long) ||
typeof(T) == typeof(ulong))
{
if (sizeof(nint) == sizeof(int))
{
return int.MaxValue;
}
// If we are on a 64 bit architecture and we are counting with a SIMD vector of 64
// bit values, we can use long.MaxValue as the upper bound, as a native integer will
// be able to contain such a value with no overflows. This will allow the count tight
// loop to process all the items in the target area in a single pass (except the mod).
// The (void*) cast is necessary to ensure the right constant is produced on runtimes
// before .NET 5 that don't natively support C# 9. For instance, removing that (void*)
// cast results in the value 0xFFFFFFFFFFFFFFFF (-1) instead of 0x7FFFFFFFFFFFFFFFF.
return (nint)(void*)long.MaxValue;
}
throw null!;
}
/// <summary>
/// Returns the difference between two <see cref="IntPtr"/> values.
/// </summary>
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
/// <param name="b">The second <see cref="IntPtr"/> value</param>
/// <returns>The difference between <paramref name="a"/> and <paramref name="b"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe IntPtr Subtract(IntPtr a, IntPtr b)
{
if (sizeof(IntPtr) == 4)
{
return (IntPtr)((int)a - (int)b);
}
return (IntPtr)((long)a - (long)b);
}
/// <summary>
/// Casts a value of a given type to <see cref="int"/>.
/// Casts a value of a given type to a native <see cref="int"/>.
/// </summary>
/// <typeparam name="T">The input type to cast.</typeparam>
/// <param name="value">The input <typeparamref name="T"/> value to cast to <see cref="int"/>.</param>
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
/// <param name="value">The input <typeparamref name="T"/> value to cast to native <see cref="int"/>.</param>
/// <returns>The native <see cref="int"/> cast of <paramref name="value"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CastToInt<T>(T value)
private static nint CastToNativeInt<T>(T value)
where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
{
return Unsafe.As<T, sbyte>(ref value);
return (byte)(sbyte)(object)value;
}
if (typeof(T) == typeof(short))
{
return Unsafe.As<T, short>(ref value);
return (ushort)(short)(object)value;
}
if (typeof(T) == typeof(int))
{
return Unsafe.As<T, int>(ref value);
return (nint)(uint)(int)(object)value;
}
if (typeof(T) == typeof(long))
{
return (int)Unsafe.As<T, long>(ref value);
return (nint)(ulong)(long)(object)value;
}
throw new NotSupportedException($"Invalid input type {typeof(T)}");
throw null!;
}
}
}

Просмотреть файл

@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -25,14 +24,13 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
public static unsafe int GetDjb2HashCode<T>(ref T r0, IntPtr length)
public static int GetDjb2HashCode<T>(ref T r0, nint length)
where T : notnull
{
int hash = 5381;
nint offset = 0;
IntPtr offset = default;
while ((byte*)length >= (byte*)8)
while (length >= 8)
{
// Doing a left shift by 5 and adding is equivalent to multiplying by 33.
// This is preferred for performance reasons, as when working with integer
@ -52,7 +50,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += 8;
}
if ((byte*)length >= (byte*)4)
if (length >= 4)
{
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
@ -63,7 +61,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += 4;
}
while ((byte*)length > (byte*)0)
while (length > 0)
{
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
@ -92,11 +90,10 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length)
public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length)
{
int hash = 5381;
IntPtr offset = default;
nint offset = 0;
// Check whether SIMD instructions are supported, and also check
// whether we have enough data to perform at least one unrolled
@ -107,7 +104,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// any preprocessing to try to get memory aligned, as that would cause
// the hash codes to potentially be different for the same data.
if (Vector.IsHardwareAccelerated &&
(byte*)length >= (byte*)(Vector<byte>.Count << 3))
length >= (Vector<byte>.Count << 3))
{
var vh = new Vector<int>(5381);
var v33 = new Vector<int>(33);
@ -115,7 +112,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// First vectorized loop, with 8 unrolled iterations.
// Assuming 256-bit registers (AVX2), a total of 256 bytes are processed
// per iteration, with the partial hashes being accumulated for later use.
while ((byte*)length >= (byte*)(Vector<byte>.Count << 3))
while (length >= (Vector<byte>.Count << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 0));
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
@ -163,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// When this loop is reached, there are up to 255 bytes left (on AVX2).
// Each iteration processed an additional 32 bytes and accumulates the results.
while ((byte*)length >= (byte*)Vector<byte>.Count)
while (length >= Vector<byte>.Count)
{
ref byte ri = ref Unsafe.Add(ref r0, offset);
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
@ -186,9 +183,9 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// Only use the loop working with 64-bit values if we are on a
// 64-bit processor, otherwise the result would be much slower.
// Each unrolled iteration processes 64 bytes.
if (sizeof(IntPtr) == sizeof(ulong))
if (sizeof(nint) == sizeof(ulong))
{
while ((byte*)length >= (byte*)(sizeof(ulong) << 3))
while (length >= (sizeof(ulong) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0));
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
@ -228,7 +225,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
// Each unrolled iteration processes 32 bytes
while ((byte*)length >= (byte*)(sizeof(uint) << 3))
while (length >= (sizeof(uint) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0));
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
@ -271,7 +268,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// left, both for the vectorized and non vectorized paths.
// That number would go up to 63 on AVX512 systems, in which case it is
// still useful to perform this last loop unrolling.
if ((byte*)length >= (byte*)(sizeof(ushort) << 3))
if (length >= (sizeof(ushort) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0));
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
@ -310,7 +307,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
// Handle the leftover items
while ((byte*)length > (byte*)0)
while (length > 0)
{
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset));

Просмотреть файл

@ -218,7 +218,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int

Просмотреть файл

@ -312,7 +312,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int

Просмотреть файл

@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Invoke(int i)
public void Invoke(int i)
{
int
low = i * this.batchSize,
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
Unsafe.AsRef(this.action).Invoke(rj);
}

Просмотреть файл

@ -0,0 +1,160 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Memory;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, int minimumActionsPerThread)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IInAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
nint
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
int
cores = Environment.ProcessorCount,
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
batchHeight = 1 + ((memory.Height - 1) / numBatches);
var actionInvoker = new InActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
// Skip the parallel invocation when possible
if (numBatches == 1)
{
actionInvoker.Invoke(0);
return;
}
// Run the batched operations in parallel
Parallel.For(
0,
numBatches,
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
actionInvoker.Invoke);
}
// Wrapping struct acting as explicit closure to execute the processing batches
private readonly struct InActionInvokerWithReadOnlyMemory2D<TItem, TAction>
where TAction : struct, IInAction<TItem>
{
private readonly int batchHeight;
private readonly ReadOnlyMemory2D<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public InActionInvokerWithReadOnlyMemory2D(
int batchHeight,
ReadOnlyMemory2D<TItem> memory,
in TAction action)
{
this.batchHeight = batchHeight;
this.memory = memory;
this.action = action;
}
/// <summary>
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
public void Invoke(int i)
{
int lowY = i * this.batchHeight;
nint highY = lowY + this.batchHeight;
int
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
width = this.memory.Width;
ReadOnlySpan2D<TItem> span = this.memory.Span;
for (int y = lowY; y < stopY; y++)
{
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
for (int x = 0; x < width; x++)
{
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
Unsafe.AsRef(this.action).Invoke(ryx);
}
}
}
}
}
}

Просмотреть файл

@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Invoke(int i)
public void Invoke(int i)
{
int
low = i * this.batchSize,
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
Unsafe.AsRef(this.action).Invoke(ref rj);
}

Просмотреть файл

@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Memory;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, int minimumActionsPerThread)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IRefAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
// The underlying data for a Memory2D<T> instance is bound to int.MaxValue in both
// axes, but its total size can exceed this value. Because of this, we calculate
// the target chunks as nint to avoid overflows, and switch back to int values
// for the rest of the setup, since the number of batches is bound to the number
// of CPU cores (which is an int), and the height of each batch is necessarily
// smaller than or equal than int.MaxValue, as it can't be greater than the
// number of total batches, which again is capped at the number of CPU cores.
nint
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
int
cores = Environment.ProcessorCount,
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
batchHeight = 1 + ((memory.Height - 1) / numBatches);
var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
// Skip the parallel invocation when possible
if (numBatches == 1)
{
actionInvoker.Invoke(0);
return;
}
// Run the batched operations in parallel
Parallel.For(
0,
numBatches,
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
actionInvoker.Invoke);
}
// Wrapping struct acting as explicit closure to execute the processing batches
private readonly struct RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>
where TAction : struct, IRefAction<TItem>
{
private readonly int batchHeight;
private readonly Memory2D<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefActionInvokerWithReadOnlyMemory2D(
int batchHeight,
Memory2D<TItem> memory,
in TAction action)
{
this.batchHeight = batchHeight;
this.memory = memory;
this.action = action;
}
/// <summary>
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
public void Invoke(int i)
{
int lowY = i * this.batchHeight;
nint highY = lowY + this.batchHeight;
int
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
width = this.memory.Width;
ReadOnlySpan2D<TItem> span = this.memory.Span;
for (int y = lowY; y < stopY; y++)
{
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
for (int x = 0; x < width; x++)
{
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
Unsafe.AsRef(this.action).Invoke(ref ryx);
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using static System.Math;
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
{
/// <summary>
/// A helper to validate arithmetic operations for <see cref="Memory2D{T}"/> and <see cref="Span2D{T}"/>.
/// </summary>
internal static class OverflowHelper
{
/// <summary>
/// Ensures that the input parameters will not exceed the maximum native int value when indexing.
/// </summary>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
/// <remarks>The input parameters are assumed to always be positive.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureIsInNativeIntRange(int height, int width, int pitch)
{
// As per the layout used in the Memory2D<T> and Span2D<T> types, we have the
// following memory representation with respect to height, width and pitch:
//
// _________width_________ ________...
// / \/
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |_
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_|
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// ...__pitch__/
//
// The indexing logic works on nint values in unchecked mode, with no overflow checks,
// which means it relies on the maximum element index to always be within <= nint.MaxValue.
// To ensure no overflows will ever occur there, we need to ensure that no instance can be
// created with parameters that could cause an overflow in case any item was accessed, so we
// need to ensure no overflows occurs when calculating the index of the last item in each view.
// The logic below calculates that index with overflow checks, throwing if one is detected.
// Note that we're subtracting 1 to the height as we don't want to include the trailing pitch
// for the 2D memory area, and also 1 to the width as the index is 0-based, as usual.
// Additionally, we're also ensuring that the stride is never greater than int.MaxValue, for
// consistency with how ND arrays work (int.MaxValue as upper bound for each axis), and to
// allow for faster iteration in the RefEnumerable<T> type, when traversing columns.
_ = checked(((nint)(width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0));
}
/// <summary>
/// Ensures that the input parameters will not exceed <see cref="int.MaxValue"/> when indexing.
/// </summary>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
/// <returns>The area resulting from the given parameters.</returns>
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
/// <remarks>The input parameters are assumed to always be positive.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeInt32Area(int height, int width, int pitch)
{
return checked(((width + pitch) * Max(unchecked(height - 1), 0)) + width);
}
}
}

Просмотреть файл

@ -0,0 +1,122 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
{
/// <summary>
/// A helper class to throw exceptions for memory types.
/// </summary>
internal static class ThrowHelper
{
/// <summary>
/// Throws an <see cref="ArgumentException"/> when using the <see langword="void"/>* constructor with a managed type.
/// </summary>
public static void ThrowArgumentExceptionForManagedType()
{
throw new ArgumentException("Can't use a void* constructor when T is a managed type");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>
public static void ThrowArgumentExceptionForDestinationTooShort()
{
throw new ArgumentException("The target span is too short to copy all the current items to");
}
/// <summary>
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
/// </summary>
public static void ThrowArrayTypeMismatchException()
{
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when using an array of an invalid type.
/// </summary>
public static void ThrowArgumentExceptionForUnsupportedType()
{
throw new ArgumentException("The specified object type is not supported");
}
/// <summary>
/// Throws an <see cref="IndexOutOfRangeException"/> when the a given coordinate is invalid.
/// </summary>
/// <remarks>
/// Throwing <see cref="IndexOutOfRangeException"/> is technically discouraged in the docs, but
/// we're doing that here for consistency with the official <see cref="Span{T}"/> type(s) from the BCL.
/// </remarks>
public static void ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when more than one parameter are invalid.
/// </summary>
public static void ThrowArgumentException()
{
throw new ArgumentException("One or more input parameters were invalid");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForDepth()
{
throw new ArgumentOutOfRangeException("depth");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForRow()
{
throw new ArgumentOutOfRangeException("row");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForColumn()
{
throw new ArgumentOutOfRangeException("column");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "offset" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForOffset()
{
throw new ArgumentOutOfRangeException("offset");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "height" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForHeight()
{
throw new ArgumentOutOfRangeException("height");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "width" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForWidth()
{
throw new ArgumentOutOfRangeException("width");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "pitch" parameter is invalid.
/// </summary>
public static void ThrowArgumentOutOfRangeExceptionForPitch()
{
throw new ArgumentOutOfRangeException("pitch");
}
}
}

Просмотреть файл

@ -0,0 +1,906 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
using Microsoft.Toolkit.HighPerformance.Memory.Views;
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <summary>
/// <see cref="Memory2D{T}"/> represents a 2D region of arbitrary memory. It is to <see cref="Span2D{T}"/>
/// what <see cref="Memory{T}"/> is to <see cref="Span{T}"/>. For further details on how the internal layout
/// is structured, see the docs for <see cref="Span2D{T}"/>. The <see cref="Memory2D{T}"/> type can wrap arrays
/// of any rank, provided that a valid series of parameters for the target memory area(s) are specified.
/// </summary>
/// <typeparam name="T">The type of items in the current <see cref="Memory2D{T}"/> instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct Memory2D<T> : IEquatable<Memory2D<T>>
{
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The pitch of the specified 2D region.
/// </summary>
private readonly int pitch;
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
public Memory2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
/// </exception>
public Memory2D(T[] array, int offset, int height, int width, int pitch)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)offset > (uint)array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = array.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
public Memory2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
this.instance = array;
this.offset = GetArray2DDataByteOffset<T>();
this.height = array.GetLength(0);
this.width = array.GetLength(1);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
public Memory2D(T[,]? array, int row, int column, int height, int width)
{
if (array is null)
{
if (row != 0 || column != 0 || height != 0 || width != 0)
{
ThrowHelper.ThrowArgumentException();
}
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
int
rows = array.GetLength(0),
columns = array.GetLength(1);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public Memory2D(T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
this.width = array.GetLength(2);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public Memory2D(T[,,] array, int depth, int row, int column, int height, int width)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
int
rows = array.GetLength(1),
columns = array.GetLength(2);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
public Memory2D(MemoryManager<T> memoryManager, int height, int width)
: this(memoryManager, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
/// </exception>
public Memory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
{
int length = memoryManager.GetSpan().Length;
if ((uint)offset > (uint)length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = memoryManager;
this.offset = (nint)(uint)offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
internal Memory2D(Memory<T> memory, int height, int width)
: this(memory, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
internal Memory2D(Memory<T> memory, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)memory.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = memory.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
// Check if the Memory<T> instance wraps a string. This is possible in case
// consumers do an unsafe cast for the entire Memory<T> object, and while not
// really safe it is still supported in CoreCLR too, so we're following suit here.
if (typeof(T) == typeof(char) &&
MemoryMarshal.TryGetString(Unsafe.As<Memory<T>, Memory<char>>(ref memory), out string? text, out int textStart, out _))
{
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
}
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
{
// Check if the input Memory<T> instance wraps an array we can access.
// This is fine, since Memory<T> on its own doesn't control the lifetime
// of the underlying array anyway, and this Memory2D<T> type would do the same.
// Using the array directly makes retrieving a Span2D<T> faster down the line,
// as we no longer have to jump through the boxed Memory<T> first anymore.
T[] array = segment.Array!;
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
}
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
{
this.instance = memoryManager;
this.offset = (nint)(uint)(memoryManagerStart + offset);
}
else
{
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
this.instance = null;
this.offset = default;
}
this.height = height;
this.width = width;
this.pitch = pitch;
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Memory2D(object instance, IntPtr offset, int height, int width, int pitch)
{
this.instance = instance;
this.offset = offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Creates a new <see cref="Memory2D{T}"/> instance from an arbitrary object.
/// </summary>
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
/// <returns>A <see cref="Memory2D{T}"/> instance with the specified parameters.</returns>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
[Pure]
public static Memory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
{
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
return new Memory2D<T>(instance, offset, height, width, pitch);
}
/// <summary>
/// Gets an empty <see cref="Memory2D{T}"/> instance.
/// </summary>
public static Memory2D<T> Empty => default;
/// <summary>
/// Gets a value indicating whether the current <see cref="Memory2D{T}"/> instance is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height == 0 || this.width == 0;
}
/// <summary>
/// Gets the length of the current <see cref="Memory2D{T}"/> instance.
/// </summary>
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)this.height * (nint)(uint)this.width;
}
/// <summary>
/// Gets the height of the underlying 2D memory area.
/// </summary>
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height;
}
/// <summary>
/// Gets the width of the underlying 2D memory area.
/// </summary>
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
/// <summary>
/// Gets a <see cref="Span2D{T}"/> instance from the current memory.
/// </summary>
public Span2D<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (!(this.instance is null))
{
#if SPAN_RUNTIME_SUPPORT
if (this.instance is MemoryManager<T> memoryManager)
{
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
return new Span2D<T>(ref r1, this.height, this.width, this.pitch);
}
else
{
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return new Span2D<T>(ref r0, this.height, this.width, this.pitch);
}
#else
return new Span2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
#endif
}
return default;
}
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="rows">The target range of rows to select.</param>
/// <param name="columns">The target range of columns to select.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
/// </exception>
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
public Memory2D<T> this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var (row, height) = rows.GetOffsetAndLength(this.height);
var (column, width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="row">The target row to map within the current instance.</param>
/// <param name="column">The target column to map within the current instance.</param>
/// <param name="height">The height to map within the current instance.</param>
/// <param name="width">The width to map within the current instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for the current instance.
/// </exception>
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
[Pure]
public Memory2D<T> Slice(int row, int column, int height, int width)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= this.width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (Height - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (this.width - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
int
shift = ((this.width + this.pitch) * row) + column,
pitch = this.pitch + (this.width - width);
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
return new Memory2D<T>(this.instance!, offset, height, width, pitch);
}
/// <summary>
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
/// <exception cref="ArgumentException">
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
/// </exception>
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
public unsafe MemoryHandle Pin()
{
if (!(this.instance is null))
{
if (this.instance is MemoryManager<T> memoryManager)
{
return memoryManager.Pin();
}
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
return new MemoryHandle(pointer, handle);
}
return default;
}
/// <summary>
/// Tries to get a <see cref="Memory{T}"/> instance, if the underlying buffer is contiguous and small enough.
/// </summary>
/// <param name="memory">The resulting <see cref="Memory{T}"/>, in case of success.</param>
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
public bool TryGetMemory(out Memory<T> memory)
{
if (this.pitch == 0 &&
Length <= int.MaxValue)
{
// Empty Memory2D<T> instance
if (this.instance is null)
{
memory = default;
}
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
{
string text = Unsafe.As<string>(this.instance);
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
// The string type could still be present if a user ends up creating a
// Memory2D<T> instance from a string using DangerousCreate. Similarly to
// how CoreCLR handles the equivalent case in Memory<T>, here we just do
// the necessary steps to still retrieve a Memory<T> instance correctly
// wrapping the target string. In this case, it is up to the caller
// to make sure not to ever actually write to the resulting Memory<T>.
memory = MemoryMarshal.AsMemory<T>(Unsafe.As<ReadOnlyMemory<char>, Memory<T>>(ref temp));
}
else if (this.instance is MemoryManager<T> memoryManager)
{
unsafe
{
// If the object is a MemoryManager<T>, just slice it as needed
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
}
}
else if (this.instance.GetType() == typeof(T[]))
{
// If it's a T[] array, also handle the initial offset
T[] array = Unsafe.As<T[]>(this.instance);
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
memory = array.AsMemory(index, this.height * this.width);
}
#if SPAN_RUNTIME_SUPPORT
else if (this.instance.GetType() == typeof(T[,]) ||
this.instance.GetType() == typeof(T[,,]))
{
// If the object is a 2D or 3D array, we can create a Memory<T> from the RawObjectMemoryManager<T> type.
// We just need to use the precomputed offset pointing to the first item in the current instance,
// and the current usable length. We don't need to retrieve the current index, as the manager just offsets.
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
}
#endif
else
{
// Reuse a single failure path to reduce
// the number of returns in the method
goto Failure;
}
return true;
}
Failure:
memory = default;
return false;
}
/// <summary>
/// Copies the contents of the current <see cref="Memory2D{T}"/> instance into a new 2D array.
/// </summary>
/// <returns>A 2D array containing the data in the current <see cref="Memory2D{T}"/> instance.</returns>
[Pure]
public T[,] ToArray() => Span.ToArray();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
{
if (obj is Memory2D<T> memory)
{
return Equals(memory);
}
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
{
return readOnlyMemory.Equals(this);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Memory2D<T> other)
{
return
this.instance == other.instance &&
this.offset == other.offset &&
this.height == other.height &&
this.width == other.width &&
this.pitch == other.pitch;
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
if (!(this.instance is null))
{
#if !NETSTANDARD1_4
return HashCode.Combine(
RuntimeHelpers.GetHashCode(this.instance),
this.offset,
this.height,
this.width,
this.pitch);
#else
Span<int> values = stackalloc int[]
{
RuntimeHelpers.GetHashCode(this.instance),
this.offset.GetHashCode(),
this.height,
this.width,
this.pitch
};
return values.GetDjb2HashCode();
#endif
}
return 0;
}
/// <inheritdoc/>
public override string ToString()
{
return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]";
}
/// <summary>
/// Defines an implicit conversion of an array to a <see cref="Memory2D{T}"/>
/// </summary>
public static implicit operator Memory2D<T>(T[,]? array) => new Memory2D<T>(array);
}
}

Просмотреть файл

@ -0,0 +1,924 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
using Microsoft.Toolkit.HighPerformance.Memory.Views;
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <summary>
/// A readonly version of <see cref="Memory2D{T}"/>.
/// </summary>
/// <typeparam name="T">The type of items in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
{
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The pitch of the specified 2D region.
/// </summary>
private readonly int pitch;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="text"/>.</remarks>
public ReadOnlyMemory2D(string text, int height, int width)
: this(text, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="text"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="text"/>.
/// </exception>
public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)text.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = text.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
public ReadOnlyMemory2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = array.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
public ReadOnlyMemory2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
this.instance = array;
this.offset = GetArray2DDataByteOffset<T>();
this.height = array.GetLength(0);
this.width = array.GetLength(1);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width)
{
if (array is null)
{
if (row != 0 || column != 0 || height != 0 || width != 0)
{
ThrowHelper.ThrowArgumentException();
}
this = default;
return;
}
int
rows = array.GetLength(0),
columns = array.GetLength(1);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth)
{
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
this.width = array.GetLength(2);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width)
{
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
int
rows = array.GetLength(1),
columns = array.GetLength(2);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width)
: this(memoryManager, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
/// </exception>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
{
int length = memoryManager.GetSpan().Length;
if ((uint)offset > (uint)length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = memoryManager;
this.offset = (nint)(uint)offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int height, int width)
: this(memory, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="ReadOnlyMemory{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)memory.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = memory.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
// Check the supported underlying objects, like in Memory2D<T>
if (typeof(T) == typeof(char) &&
MemoryMarshal.TryGetString(Unsafe.As<ReadOnlyMemory<T>, ReadOnlyMemory<char>>(ref memory), out string? text, out int textStart, out _))
{
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
}
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
{
T[] array = segment.Array!;
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
}
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
{
this.instance = memoryManager;
this.offset = (nint)(uint)(memoryManagerStart + offset);
}
else
{
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
this.instance = null;
this.offset = default;
}
this.height = height;
this.width = width;
this.pitch = pitch;
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch)
{
this.instance = instance;
this.offset = offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Creates a new <see cref="ReadOnlyMemory2D{T}"/> instance from an arbitrary object.
/// </summary>
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
/// <returns>A <see cref="ReadOnlyMemory2D{T}"/> instance with the specified parameters.</returns>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
[Pure]
public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
{
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
return new ReadOnlyMemory2D<T>(instance, offset, height, width, pitch);
}
/// <summary>
/// Gets an empty <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public static ReadOnlyMemory2D<T> Empty => default;
/// <summary>
/// Gets a value indicating whether the current <see cref="ReadOnlyMemory2D{T}"/> instance is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height == 0 || this.width == 0;
}
/// <summary>
/// Gets the length of the current <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)this.height * (nint)(uint)this.width;
}
/// <summary>
/// Gets the height of the underlying 2D memory area.
/// </summary>
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height;
}
/// <summary>
/// Gets the width of the underlying 2D memory area.
/// </summary>
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
/// <summary>
/// Gets a <see cref="ReadOnlySpan2D{T}"/> instance from the current memory.
/// </summary>
public ReadOnlySpan2D<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (!(this.instance is null))
{
#if SPAN_RUNTIME_SUPPORT
if (this.instance is MemoryManager<T> memoryManager)
{
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
return new ReadOnlySpan2D<T>(r1, this.height, this.width, this.pitch);
}
else
{
// This handles both arrays and strings
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return new ReadOnlySpan2D<T>(r0, this.height, this.width, this.pitch);
}
#else
return new ReadOnlySpan2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
#endif
}
return default;
}
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="rows">The target range of rows to select.</param>
/// <param name="columns">The target range of columns to select.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
public ReadOnlyMemory2D<T> this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var (row, height) = rows.GetOffsetAndLength(this.height);
var (column, width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="row">The target row to map within the current instance.</param>
/// <param name="column">The target column to map within the current instance.</param>
/// <param name="height">The height to map within the current instance.</param>
/// <param name="width">The width to map within the current instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for the current instance.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
[Pure]
public ReadOnlyMemory2D<T> Slice(int row, int column, int height, int width)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= this.width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (Height - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (this.width - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
int
shift = ((this.width + this.pitch) * row) + column,
pitch = this.pitch + (this.width - width);
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
return new ReadOnlyMemory2D<T>(this.instance!, offset, height, width, pitch);
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
/// <exception cref="ArgumentException">
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
/// </exception>
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
public unsafe MemoryHandle Pin()
{
if (!(this.instance is null))
{
if (this.instance is MemoryManager<T> memoryManager)
{
return memoryManager.Pin();
}
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
return new MemoryHandle(pointer, handle);
}
return default;
}
/// <summary>
/// Tries to get a <see cref="ReadOnlyMemory{T}"/> instance, if the underlying buffer is contiguous and small enough.
/// </summary>
/// <param name="memory">The resulting <see cref="ReadOnlyMemory{T}"/>, in case of success.</param>
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
public bool TryGetMemory(out ReadOnlyMemory<T> memory)
{
if (this.pitch == 0 &&
Length <= int.MaxValue)
{
// Empty Memory2D<T> instance
if (this.instance is null)
{
memory = default;
}
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
{
// Here we need to create a Memory<char> from the wrapped string, and to do so we need to do an inverse
// lookup to find the initial index of the string with respect to the byte offset we're currently using,
// which refers to the raw string object data. This can include variable padding or other additional
// fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset
// difference between the start of the Span<char> (which directly wraps just the actual character data
// within the string), and the input reference, which we can get from the byte offset in use. The result
// is the character index which we can use to create the final Memory<char> instance.
string text = Unsafe.As<string>(this.instance);
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
memory = Unsafe.As<ReadOnlyMemory<char>, ReadOnlyMemory<T>>(ref temp);
}
else if (this.instance is MemoryManager<T> memoryManager)
{
unsafe
{
// If the object is a MemoryManager<T>, just slice it as needed
memory = memoryManager.Memory.Slice((int)(void*)this.offset, this.height * this.width);
}
}
else if (this.instance.GetType() == typeof(T[]))
{
// If it's a T[] array, also handle the initial offset
T[] array = Unsafe.As<T[]>(this.instance);
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
memory = array.AsMemory(index, this.height * this.width);
}
#if SPAN_RUNTIME_SUPPORT
else if (this.instance.GetType() == typeof(T[,]) ||
this.instance.GetType() == typeof(T[,,]))
{
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
}
#endif
else
{
// Reuse a single failure path to reduce
// the number of returns in the method
goto Failure;
}
return true;
}
Failure:
memory = default;
return false;
}
/// <summary>
/// Copies the contents of the current <see cref="ReadOnlyMemory2D{T}"/> instance into a new 2D array.
/// </summary>
/// <returns>A 2D array containing the data in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
[Pure]
public T[,] ToArray() => Span.ToArray();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
{
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
{
return Equals(readOnlyMemory);
}
if (obj is Memory2D<T> memory)
{
return Equals(memory);
}
return false;
}
/// <inheritdoc/>
public bool Equals(ReadOnlyMemory2D<T> other)
{
return
this.instance == other.instance &&
this.offset == other.offset &&
this.height == other.height &&
this.width == other.width &&
this.pitch == other.pitch;
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
if (!(this.instance is null))
{
#if !NETSTANDARD1_4
return HashCode.Combine(
RuntimeHelpers.GetHashCode(this.instance),
this.offset,
this.height,
this.width,
this.pitch);
#else
Span<int> values = stackalloc int[]
{
RuntimeHelpers.GetHashCode(this.instance),
this.offset.GetHashCode(),
this.height,
this.width,
this.pitch
};
return values.GetDjb2HashCode();
#endif
}
return 0;
}
/// <inheritdoc/>
public override string ToString()
{
return $"Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]";
}
/// <summary>
/// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new ReadOnlyMemory2D<T>(array);
/// <summary>
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory) => Unsafe.As<Memory2D<T>, ReadOnlyMemory2D<T>>(ref memory);
}
}

Просмотреть файл

@ -0,0 +1,201 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <inheritdoc cref="ReadOnlySpan2D{T}"/>
public readonly ref partial struct ReadOnlySpan2D<T>
{
/// <summary>
/// Gets an enumerable that traverses items in a specified row.
/// </summary>
/// <param name="row">The target row to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRefEnumerable<T> GetRow(int row)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
nint startIndex = (nint)(uint)this.stride * (nint)(uint)row;
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Width, 1);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, this.width, 1);
#endif
}
/// <summary>
/// Gets an enumerable that traverses items in a specified column.
/// </summary>
/// <param name="column">The target column to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRefEnumerable<T> GetColumn(int column)
{
if ((uint)column >= Width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Height, this.stride);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, Height, this.stride);
#endif
}
/// <summary>
/// Returns an enumerator for the current <see cref="ReadOnlySpan2D{T}"/> instance.
/// </summary>
/// <returns>
/// An enumerator that can be used to traverse the items in the current <see cref="ReadOnlySpan2D{T}"/> instance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>Just like in <see cref="ReadOnlySpan2D{T}"/>, the length is the height of the 2D region.</remarks>
private readonly ReadOnlySpan<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
#endif
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The stride of the specified 2D region.
/// </summary>
private readonly int stride;
/// <summary>
/// The current horizontal offset.
/// </summary>
private int x;
/// <summary>
/// The current vertical offset.
/// </summary>
private int y;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The target <see cref="ReadOnlySpan2D{T}"/> instance to enumerate.</param>
internal Enumerator(ReadOnlySpan2D<T> span)
{
#if SPAN_RUNTIME_SUPPORT
this.span = span.span;
#else
this.instance = span.instance;
this.offset = span.offset;
this.height = span.height;
#endif
this.width = span.width;
this.stride = span.stride;
this.x = -1;
this.y = 0;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int x = this.x + 1;
// Horizontal move, within range
if (x < this.width)
{
this.x = x;
return true;
}
// We reached the end of a row and there is at least
// another row available: wrap to a new line and continue.
this.x = 0;
#if SPAN_RUNTIME_SUPPORT
return ++this.y < this.span.Length;
#else
return ++this.y < this.height;
#endif
}
/// <summary>
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
/// </summary>
public readonly ref readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
return ref Unsafe.Add(ref r0, index);
}
}
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,201 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <inheritdoc cref="Span2D{T}"/>
public readonly ref partial struct Span2D<T>
{
/// <summary>
/// Gets an enumerable that traverses items in a specified row.
/// </summary>
/// <param name="row">The target row to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefEnumerable<T> GetRow(int row)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
nint startIndex = (nint)(uint)this.Stride * (nint)(uint)row;
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
#if SPAN_RUNTIME_SUPPORT
return new RefEnumerable<T>(ref r1, Width, 1);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
return new RefEnumerable<T>(this.Instance, offset, this.width, 1);
#endif
}
/// <summary>
/// Gets an enumerable that traverses items in a specified column.
/// </summary>
/// <param name="column">The target column to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefEnumerable<T> GetColumn(int column)
{
if ((uint)column >= Width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
#if SPAN_RUNTIME_SUPPORT
return new RefEnumerable<T>(ref r1, Height, this.Stride);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
return new RefEnumerable<T>(this.Instance, offset, Height, this.Stride);
#endif
}
/// <summary>
/// Returns an enumerator for the current <see cref="Span2D{T}"/> instance.
/// </summary>
/// <returns>
/// An enumerator that can be used to traverse the items in the current <see cref="Span2D{T}"/> instance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="Span2D{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>Just like in <see cref="Span2D{T}"/>, the length is the height of the 2D region.</remarks>
private readonly Span<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
#endif
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The stride of the specified 2D region.
/// </summary>
private readonly int stride;
/// <summary>
/// The current horizontal offset.
/// </summary>
private int x;
/// <summary>
/// The current vertical offset.
/// </summary>
private int y;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The target <see cref="Span2D{T}"/> instance to enumerate.</param>
internal Enumerator(Span2D<T> span)
{
#if SPAN_RUNTIME_SUPPORT
this.span = span.span;
#else
this.instance = span.Instance;
this.offset = span.Offset;
this.height = span.height;
#endif
this.width = span.width;
this.stride = span.Stride;
this.x = -1;
this.y = 0;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int x = this.x + 1;
// Horizontal move, within range
if (x < this.width)
{
this.x = x;
return true;
}
// We reached the end of a row and there is at least
// another row available: wrap to a new line and continue.
this.x = 0;
#if SPAN_RUNTIME_SUPPORT
return ++this.y < this.span.Length;
#else
return ++this.y < this.height;
#endif
}
/// <summary>
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
/// </summary>
public readonly ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
return ref Unsafe.Add(ref r0, index);
}
}
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -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.Memory.Views
{
/// <summary>
/// A debug proxy used to display items in a 2D layout.
/// </summary>
/// <typeparam name="T">The type of items to display.</typeparam>
internal sealed class MemoryDebugView2D<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memory">The input <see cref="Memory2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(Memory2D<T> memory)
{
this.Items = memory.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(ReadOnlyMemory2D<T> memory)
{
this.Items = memory.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="span">The input <see cref="Span2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(Span2D<T> span)
{
this.Items = span.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="span">The input <see cref="ReadOnlySpan2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(ReadOnlySpan2D<T> span)
{
this.Items = span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public T[,]? Items { get; }
}
}

Просмотреть файл

@ -2,18 +2,19 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Windows Community Toolkit High Performance .NET Standard</Title>
<Description>
This package includes high performance .NET Standard helpers such as:
- Memory2D&lt;T&gt; and Span2D&lt;T&gt;: two types providing fast and allocation-free abstraction over 2D memory areas.
- ArrayPoolBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt; implementation using pooled arrays, which also supports IMemoryOwner&lt;T&gt;.
- MemoryBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt;: implementation that can wrap external Memory&lt;T&gt;: instances.
- MemoryOwner&lt;T&gt;: an IMemoryOwner&lt;T&gt; implementation with an embedded length and a fast Span&lt;T&gt; accessor.
- SpanOwner&lt;T&gt;: a stack-only type with the ability to rent a buffer of a specified length and getting a Span&lt;T&gt; from it.
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
- String, array, Span&lt;T&gt;, Memory&lt;T&gt; extensions and more, all focused on high performance.
- String, array, Memory&lt;T&gt;, Span&lt;T&gt; extensions and more, all focused on high performance.
- HashCode&lt;T&gt;: a SIMD-enabled extension of HashCode to quickly process sequences of values.
- BitHelper: a class with helper methods to perform bit operations on numeric types.
- ParallelHelper: helpers to work with parallel code in a highly optimized manner.

Просмотреть файл

@ -36,6 +36,16 @@ namespace Microsoft.Toolkit.HighPerformance
Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="pointer">The pointer to the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ReadOnlyRef(void* pointer)
: this(Unsafe.AsRef<T>(pointer))
{
}
/// <summary>
/// Gets the readonly <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>

Просмотреть файл

@ -34,6 +34,16 @@ namespace Microsoft.Toolkit.HighPerformance
Span = MemoryMarshal.CreateSpan(ref value, 1);
}
/// <summary>
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
/// </summary>
/// <param name="pointer">The pointer to the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Ref(void* pointer)
: this(ref Unsafe.AsRef<T>(pointer))
{
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>

Просмотреть файл

@ -13,14 +13,14 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
internal static partial class MemoryStream
{
/// <summary>
/// Validates the <see cref="Stream.Position"/> argument.
/// Validates the <see cref="Stream.Position"/> argument (it needs to be in the [0, length]) range.
/// </summary>
/// <param name="position">The new <see cref="Stream.Position"/> value being set.</param>
/// <param name="length">The maximum length of the target <see cref="Stream"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePosition(long position, int length)
{
if ((ulong)position >= (ulong)length)
if ((ulong)position > (ulong)length)
{
ThrowArgumentOutOfRangeExceptionForPosition();
}

Просмотреть файл

@ -49,6 +49,16 @@
HorizontalSpacing="@[HorizontalSpacing:Slider:5:0-200]@" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- Change those values to change the WrapPanel's children alignment -->
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Page>

Просмотреть файл

@ -64,8 +64,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
Category = "Remove",
Thumbnail = "ms-appx:///Assets/Photos/BigFourSummerHeat.jpg",
Width = Rand.Next(120, 180),
Height = Rand.Next(80, 130)
Width = Rand.Next(60, 180),
Height = Rand.Next(40, 140)
});
}

Просмотреть файл

@ -1,4 +1,6 @@
# How to add new samples
For the latest info, [visit the wiki article here](https://github.com/windows-toolkit/WindowsCommunityToolkit/wiki/Sample-Development).
# How to add new samples
This document describes how to add a new sample page for a new control you want to add to the toolkit.

Просмотреть файл

@ -3,8 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Toolkit.Uwp.UI.Automation.Peers;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
@ -76,6 +78,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
Collapsed?.Invoke(this, args);
}
/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
/// <returns>An automation peer for this <see cref="Expander"/>.</returns>
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ExpanderAutomationPeer(this);
}
private void ExpanderToggleButtonPart_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key != VirtualKey.Enter)

Просмотреть файл

@ -0,0 +1,116 @@
// 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 Microsoft.Toolkit.Uwp.UI.Controls;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider;
namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers
{
/// <summary>
/// Defines a framework element automation peer for the <see cref="Expander"/> control.
/// </summary>
public class ExpanderAutomationPeer : FrameworkElementAutomationPeer, IToggleProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpanderAutomationPeer"/> class.
/// </summary>
/// <param name="owner">
/// The <see cref="Expander" /> that is associated with this <see cref="T:Windows.UI.Xaml.Automation.Peers.ExpanderAutomationPeer" />.
/// </param>
public ExpanderAutomationPeer(Expander owner)
: base(owner)
{
}
/// <summary>Gets the toggle state of the control.</summary>
/// <returns>The toggle state of the control, as a value of the enumeration.</returns>
public ToggleState ToggleState => OwningExpander.IsExpanded ? ToggleState.On : ToggleState.Off;
private Expander OwningExpander
{
get
{
return Owner as Expander;
}
}
/// <summary>Cycles through the toggle states of a control.</summary>
public void Toggle()
{
if (!IsEnabled())
{
throw new ElementNotEnabledException();
}
OwningExpander.IsExpanded = !OwningExpander.IsExpanded;
}
/// <summary>
/// Gets the control type for the element that is associated with the UI Automation peer.
/// </summary>
/// <returns>The control type.</returns>
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
/// <summary>
/// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType,
/// differentiates the control represented by this AutomationPeer.
/// </summary>
/// <returns>The string that contains the name.</returns>
protected override string GetClassNameCore()
{
return Owner.GetType().Name;
}
/// <summary>
/// Called by GetName.
/// </summary>
/// <returns>
/// Returns the first of these that is not null or empty:
/// - Value returned by the base implementation
/// - Name of the owning Expander
/// - Expander class name
/// </returns>
protected override string GetNameCore()
{
string name = base.GetNameCore();
if (!string.IsNullOrEmpty(name))
{
return name;
}
if (this.OwningExpander != null)
{
name = this.OwningExpander.Name;
}
if (string.IsNullOrEmpty(name))
{
name = this.GetClassName();
}
return name;
}
/// <summary>
/// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface.
/// </summary>
/// <param name="patternInterface">A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration.</param>
/// <returns>The object that supports the specified pattern, or null if unsupported.</returns>
protected override object GetPatternCore(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.Toggle:
return this;
}
return base.GetPatternCore(patternInterface);
}
}
}

Просмотреть файл

@ -469,6 +469,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
{
OnValueChanged(this);
base.OnValueChanged(oldValue, newValue);
if (AutomationPeer.ListenerExists(AutomationEvents.LiveRegionChanged))
{
var peer = FrameworkElementAutomationPeer.FromElement(this) as RadialGaugeAutomationPeer;
peer?.RaiseValueChangedEvent(oldValue, newValue);
}
}
private static void OnValueChanged(DependencyObject d)

Просмотреть файл

@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider;
@ -12,7 +14,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// Exposes <see cref="RadialGauge"/> to Microsoft UI Automation.
/// </summary>
public class RadialGaugeAutomationPeer :
FrameworkElementAutomationPeer,
RangeBaseAutomationPeer,
IRangeValueProvider
{
/// <summary>
@ -25,25 +27,25 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
/// <inheritdoc/>
public bool IsReadOnly => !((RadialGauge)Owner).IsInteractive;
public new bool IsReadOnly => !((RadialGauge)Owner).IsInteractive;
/// <inheritdoc/>
public double LargeChange => ((RadialGauge)Owner).StepSize;
public new double LargeChange => ((RadialGauge)Owner).StepSize;
/// <inheritdoc/>
public double Maximum => ((RadialGauge)Owner).Maximum;
public new double Maximum => ((RadialGauge)Owner).Maximum;
/// <inheritdoc/>
public double Minimum => ((RadialGauge)Owner).Minimum;
public new double Minimum => ((RadialGauge)Owner).Minimum;
/// <inheritdoc/>
public double SmallChange => ((RadialGauge)Owner).StepSize;
public new double SmallChange => ((RadialGauge)Owner).StepSize;
/// <inheritdoc/>
public double Value => ((RadialGauge)Owner).Value;
public new double Value => ((RadialGauge)Owner).Value;
/// <inheritdoc/>
public void SetValue(double value)
public new void SetValue(double value)
{
((RadialGauge)Owner).Value = value;
}
@ -58,7 +60,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
protected override string GetNameCore()
{
var gauge = (RadialGauge)Owner;
return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified. " : "unit " + gauge.Unit + ". ");
return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified, " : "unit " + gauge.Unit + ", ") + Value;
}
/// <inheritdoc/>
@ -78,5 +80,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
{
return AutomationControlType.Custom;
}
/// <summary>
/// Raises the property changed event for this AutomationPeer for the provided identifier.
/// </summary>
/// <param name="oldValue">Old value</param>
/// <param name="newValue">New value</param>
public void RaiseValueChangedEvent(double oldValue, double newValue)
{
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, PropertyValue.CreateDouble(oldValue), PropertyValue.CreateDouble(newValue));
}
}
}

Просмотреть файл

@ -2,11 +2,9 @@
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Drawing;
using Microsoft.Toolkit.Diagnostics;
namespace Microsoft.Toolkit.Uwp.UI.Controls
{
@ -16,23 +14,81 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <see cref="UniformGrid.GetFreeSpot"/> iterator.
/// This is used so we can better isolate our logic and make it easier to test.
/// </summary>
internal class TakenSpotsReferenceHolder
internal sealed class TakenSpotsReferenceHolder
{
/// <summary>
/// Gets or sets the array to hold taken spots.
/// True value indicates an item in the layout is fixed to that position.
/// False values indicate free openings where an item can be placed.
/// The <see cref="BitArray"/> instance used to efficiently track empty spots.
/// </summary>
public bool[,] SpotsTaken { get; set; }
private readonly BitArray spotsTaken;
/// <summary>
/// Initializes a new instance of the <see cref="TakenSpotsReferenceHolder"/> class.
/// </summary>
/// <param name="rows">The number of rows to track.</param>
/// <param name="columns">The number of columns to track.</param>
public TakenSpotsReferenceHolder(int rows, int columns)
{
SpotsTaken = new bool[rows, columns];
Guard.IsGreaterThanOrEqualTo(rows, 0, nameof(rows));
Guard.IsGreaterThanOrEqualTo(columns, 0, nameof(columns));
Height = rows;
Width = columns;
this.spotsTaken = new BitArray(rows * columns);
}
public TakenSpotsReferenceHolder(bool[,] array)
/// <summary>
/// Gets the height of the grid to monitor.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the width of the grid to monitor.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets or sets the value of a specified grid cell.
/// </summary>
/// <param name="i">The vertical offset.</param>
/// <param name="j">The horizontal offset.</param>
public bool this[int i, int j]
{
SpotsTaken = array;
get => this.spotsTaken[(i * Width) + j];
set => this.spotsTaken[(i * Width) + j] = value;
}
/// <summary>
/// Fills the specified area in the current grid with a given value.
/// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
/// </summary>
/// <param name="value">The value to fill the target area with.</param>
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
/// <param name="width">The positive width of area to fill.</param>
/// <param name="height">The positive height of area to fill.</param>
public void Fill(bool value, int row, int column, int width, int height)
{
Rectangle bounds = new Rectangle(0, 0, Width, Height);
// 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++)
{
for (int j = bounds.Left; j < bounds.Right; j++)
{
this[i, j] = value;
}
}
}
/// <summary>
/// Resets the current reference holder.
/// </summary>
public void Reset()
{
this.spotsTaken.SetAll(false);
}
}
}

Просмотреть файл

@ -22,16 +22,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
{
if (topdown)
{
var rows = arrayref.SpotsTaken.GetLength(0);
var rows = arrayref.Height;
// Layout spots from Top-Bottom, Left-Right (right-left handled automatically by Grid with Flow-Direction).
// Effectively transpose the Grid Layout.
for (int c = 0; c < arrayref.SpotsTaken.GetLength(1); c++)
for (int c = 0; c < arrayref.Width; c++)
{
int start = (c == 0 && firstcolumn > 0 && firstcolumn < rows) ? firstcolumn : 0;
for (int r = start; r < rows; r++)
{
if (!arrayref.SpotsTaken[r, c])
if (!arrayref[r, c])
{
yield return (r, c);
}
@ -40,17 +40,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
else
{
var columns = arrayref.SpotsTaken.GetLength(1);
var columns = arrayref.Width;
// Layout spots as normal from Left-Right.
// (right-left handled automatically by Grid with Flow-Direction
// during its layout, internal model is always left-right).
for (int r = 0; r < arrayref.SpotsTaken.GetLength(0); r++)
for (int r = 0; r < arrayref.Height; r++)
{
int start = (r == 0 && firstcolumn > 0 && firstcolumn < columns) ? firstcolumn : 0;
for (int c = start; c < columns; c++)
{
if (!arrayref.SpotsTaken[r, c])
if (!arrayref[r, c])
{
yield return (r, c);
}

Просмотреть файл

@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Toolkit.Extensions;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -20,6 +19,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
// Internal list we use to keep track of items that we don't have space to layout.
private List<UIElement> _overflow = new List<UIElement>();
/// <summary>
/// The <see cref="TakenSpotsReferenceHolder"/> instance in use, if any.
/// </summary>
private TakenSpotsReferenceHolder _spotref;
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
@ -36,7 +40,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
SetupRowDefinitions(rows);
SetupColumnDefinitions(columns);
var spotref = new TakenSpotsReferenceHolder(rows, columns);
TakenSpotsReferenceHolder spotref;
// If the last spot holder matches the size currently in use, just reset
// that instance and reuse it to avoid allocating a new bit array.
if (_spotref != null && _spotref.Height == rows && _spotref.Width == columns)
{
spotref = _spotref;
spotref.Reset();
}
else
{
spotref = _spotref = new TakenSpotsReferenceHolder(rows, columns);
}
// Figure out which children we should automatically layout and where available openings are.
foreach (var child in visible)
@ -56,7 +73,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
else
{
SetAutoLayout(child, false);
spotref.SpotsTaken.Fill(true, row, col, colspan, rowspan); // row, col, width, height
spotref.Fill(true, row, col, colspan, rowspan);
}
}
@ -100,7 +118,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
if (rowspan > 1 || colspan > 1)
{
// TODO: Need to tie this into iterator
spotref.SpotsTaken.Fill(true, row, column, GetColumnSpan(child), GetRowSpan(child)); // row, col, width, height
spotref.Fill(true, row, column, colspan, rowspan);
}
}
else
@ -135,7 +153,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
// Return our desired size based on the largest child we found, our dimensions, and spacing.
var desiredSize = new Size((maxWidth * (double)columns) + columnSpacingSize, (maxHeight * (double)rows) + rowSpacingSize);
var desiredSize = new Size((maxWidth * columns) + columnSpacingSize, (maxHeight * rows) + rowSpacingSize);
// Required to perform regular grid measurement, but ignore result.
base.MeasureOverride(desiredSize);

Просмотреть файл

@ -2,6 +2,10 @@
// 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 Microsoft.Toolkit.Diagnostics;
using Windows.Foundation;
using Windows.UI.Xaml.Controls;
namespace Microsoft.Toolkit.Uwp.UI.Controls
@ -14,12 +18,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
[System.Diagnostics.DebuggerDisplay("U = {U} V = {V}")]
private struct UvMeasure
{
internal static readonly UvMeasure Zero = default(UvMeasure);
internal static UvMeasure Zero => default;
internal double U { get; set; }
internal double V { get; set; }
public UvMeasure(Orientation orientation, Size size)
: this(orientation, size.Width, size.Height)
{
}
public UvMeasure(Orientation orientation, double width, double height)
{
if (orientation == Orientation.Horizontal)
@ -33,6 +42,56 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
V = width;
}
}
public UvMeasure Add(double u, double v)
=> new UvMeasure { U = U + u, V = V + v };
public UvMeasure Add(UvMeasure measure)
=> Add(measure.U, measure.V);
public Size ToSize(Orientation orientation)
=> orientation == Orientation.Horizontal ? new Size(U, V) : new Size(V, U);
}
private struct UvRect
{
public UvMeasure Position { get; set; }
public UvMeasure Size { get; set; }
public Rect ToRect(Orientation orientation) => orientation switch
{
Orientation.Vertical => new Rect(Position.V, Position.U, Size.V, Size.U),
Orientation.Horizontal => new Rect(Position.U, Position.V, Size.U, Size.V),
_ => ThrowHelper.ThrowNotSupportedException<Rect>("unsupported orientation"),
};
}
private struct Row
{
public Row(List<UvRect> childrenRects, UvMeasure size)
{
ChildrenRects = childrenRects;
Size = size;
}
public List<UvRect> ChildrenRects { get; }
public UvMeasure Size { get; set; }
public UvRect Rect => ChildrenRects.Count > 0 ?
new UvRect { Position = ChildrenRects[0].Position, Size = Size } :
new UvRect { Position = UvMeasure.Zero, Size = Size };
public void Add(UvMeasure position, UvMeasure size)
{
ChildrenRects.Add(new UvRect { Position = position, Size = size });
Size = new UvMeasure
{
U = position.U + size.U,
V = Math.Max(Size.V, size.V),
};
}
}
}
}

Просмотреть файл

@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -128,133 +130,130 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
}
private readonly List<Row> _rows = new List<Row>();
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
availableSize.Width = availableSize.Width - Padding.Left - Padding.Right;
availableSize.Height = availableSize.Height - Padding.Top - Padding.Bottom;
var totalMeasure = UvMeasure.Zero;
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var lineMeasure = UvMeasure.Zero;
var childAvailableSize = new Size(
availableSize.Width - Padding.Left - Padding.Right,
availableSize.Height - Padding.Top - Padding.Bottom);
foreach (var child in Children)
{
child.Measure(availableSize);
var currentMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (currentMeasure.U == 0)
{
continue; // ignore collapsed items
}
// if this is the first item, do not add spacing. Spacing is added to the "left"
double uChange = lineMeasure.U == 0
? currentMeasure.U
: currentMeasure.U + spacingMeasure.U;
if (parentMeasure.U >= uChange + lineMeasure.U)
{
lineMeasure.U += uChange;
lineMeasure.V = Math.Max(lineMeasure.V, currentMeasure.V);
}
else
{
// new line should be added
// to get the max U to provide it correctly to ui width ex: ---| or -----|
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
totalMeasure.V += lineMeasure.V + spacingMeasure.V;
// if the next new row still can handle more controls
if (parentMeasure.U > currentMeasure.U)
{
// set lineMeasure initial values to the currentMeasure to be calculated later on the new loop
lineMeasure = currentMeasure;
}
// the control will take one row alone
else
{
// validate the new control measures
totalMeasure.U = Math.Max(currentMeasure.U, totalMeasure.U);
totalMeasure.V += currentMeasure.V;
// add new empty line
lineMeasure = UvMeasure.Zero;
}
}
child.Measure(childAvailableSize);
}
// update value with the last line
// if the last loop is (parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it
// if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here
// for the last condition it is zeros so adding it will make no difference
// this way is faster than an if condition in every loop for checking the last item
totalMeasure.U = Math.Max(lineMeasure.U, totalMeasure.U);
totalMeasure.V += lineMeasure.V;
totalMeasure.U = Math.Ceiling(totalMeasure.U);
return Orientation == Orientation.Horizontal ? new Size(totalMeasure.U, totalMeasure.V) : new Size(totalMeasure.V, totalMeasure.U);
var requiredSize = UpdateRows(availableSize);
return requiredSize;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
if (Children.Count > 0)
if ((Orientation == Orientation.Horizontal && finalSize.Width < DesiredSize.Width) ||
(Orientation == Orientation.Vertical && finalSize.Height < DesiredSize.Height))
{
var parentMeasure = new UvMeasure(Orientation, finalSize.Width, finalSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
// We haven't received our desired size. We need to refresh the rows.
UpdateRows(finalSize);
}
double currentV = 0;
void Arrange(UIElement child, bool isLast = false)
if (_rows.Count > 0)
{
// Now that we have all the data, we do the actual arrange pass
var childIndex = 0;
foreach (var row in _rows)
{
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (desiredMeasure.U == 0)
foreach (var rect in row.ChildrenRects)
{
return; // if an item is collapsed, avoid adding the spacing
}
var child = Children[childIndex++];
var arrangeRect = new UvRect
{
Position = rect.Position,
Size = new UvMeasure { U = rect.Size.U, V = row.Size.V },
};
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
{
// next row!
position.U = paddingStart.U;
position.V += currentV + spacingMeasure.V;
currentV = 0;
var finalRect = arrangeRect.ToRect(Orientation);
child.Arrange(finalRect);
}
// Stretch the last item to fill the available space
if (isLast)
{
desiredMeasure.U = parentMeasure.U - position.U;
}
// place the item
if (Orientation == Orientation.Horizontal)
{
child.Arrange(new Rect(position.U, position.V, desiredMeasure.U, desiredMeasure.V));
}
else
{
child.Arrange(new Rect(position.V, position.U, desiredMeasure.V, desiredMeasure.U));
}
// adjust the location for the next items
position.U += desiredMeasure.U + spacingMeasure.U;
currentV = Math.Max(desiredMeasure.V, currentV);
}
var lastIndex = Children.Count - 1;
for (var i = 0; i < lastIndex; i++)
{
Arrange(Children[i]);
}
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
}
return finalSize;
}
private Size UpdateRows(Size availableSize)
{
_rows.Clear();
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
if (Children.Count == 0)
{
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
return emptySize;
}
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
var currentRow = new Row(new List<UvRect>(), default);
var finalMeasure = new UvMeasure(Orientation, width: 0.0, height: 0.0);
void Arrange(UIElement child, bool isLast = false)
{
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize);
if (desiredMeasure.U == 0)
{
return; // if an item is collapsed, avoid adding the spacing
}
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
{
// next row!
position.U = paddingStart.U;
position.V += currentRow.Size.V + spacingMeasure.V;
_rows.Add(currentRow);
currentRow = new Row(new List<UvRect>(), default);
}
// Stretch the last item to fill the available space
if (isLast)
{
desiredMeasure.U = parentMeasure.U - position.U;
}
currentRow.Add(position, desiredMeasure);
// adjust the location for the next items
position.U += desiredMeasure.U + spacingMeasure.U;
finalMeasure.U = Math.Max(finalMeasure.U, position.U);
}
var lastIndex = Children.Count - 1;
for (var i = 0; i < lastIndex; i++)
{
Arrange(Children[i]);
}
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
if (currentRow.ChildrenRects.Count > 0)
{
_rows.Add(currentRow);
}
if (_rows.Count == 0)
{
var emptySize = paddingStart.Add(paddingEnd).ToSize(Orientation);
return emptySize;
}
// Get max V here before computing final rect
var lastRowRect = _rows.Last().Rect;
finalMeasure.V = lastRowRect.Position.V + lastRowRect.Size.V;
var finalRect = finalMeasure.Add(paddingEnd).ToSize(Orientation);
return finalRect;
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Toolkit.Extensions
{
@ -13,71 +14,6 @@ namespace Microsoft.Toolkit.Extensions
/// </summary>
public static class ArrayExtensions
{
/// <summary>
/// Fills elements of a rectangular array at the given position and size to a specific value.
/// Ranges given will fill in as many elements as possible, ignoring positions outside the bounds of the array.
/// </summary>
/// <typeparam name="T">The element type of the array.</typeparam>
/// <param name="array">The source array.</param>
/// <param name="value">Value to fill with.</param>
/// <param name="row">Row to start on (inclusive, zero-index).</param>
/// <param name="col">Column to start on (inclusive, zero-index).</param>
/// <param name="width">Positive width of area to fill.</param>
/// <param name="height">Positive height of area to fill.</param>
public static void Fill<T>(this T[,] array, T value, int row, int col, int width, int height)
{
for (int r = row; r < row + height; r++)
{
for (int c = col; c < col + width; c++)
{
if (r >= 0 && c >= 0 && r < array.GetLength(0) && c < array.GetLength(1))
{
array[r, c] = value;
}
}
}
}
/// <summary>
/// Yields a row from a rectangular array.
/// </summary>
/// <typeparam name="T">The element type of the array.</typeparam>
/// <param name="rectarray">The source array.</param>
/// <param name="row">Row record to retrieve, 0-based index.</param>
/// <returns>Yielded row.</returns>
public static IEnumerable<T> GetRow<T>(this T[,] rectarray, int row)
{
if (row < 0 || row >= rectarray.GetLength(0))
{
throw new ArgumentOutOfRangeException(nameof(row));
}
for (int c = 0; c < rectarray.GetLength(1); c++)
{
yield return rectarray[row, c];
}
}
/// <summary>
/// Yields a column from a rectangular array.
/// </summary>
/// <typeparam name="T">The element type of the array.</typeparam>
/// <param name="rectarray">The source array.</param>
/// <param name="column">Column record to retrieve, 0-based index.</param>
/// <returns>Yielded column.</returns>
public static IEnumerable<T> GetColumn<T>(this T[,] rectarray, int column)
{
if (column < 0 || column >= rectarray.GetLength(1))
{
throw new ArgumentOutOfRangeException(nameof(column));
}
for (int r = 0; r < rectarray.GetLength(0); r++)
{
yield return rectarray[r, column];
}
}
/// <summary>
/// Yields a column from a jagged array.
/// An exception will be thrown if the column is out of bounds, and return default in places where there are no elements from inner arrays.
@ -98,7 +34,7 @@ namespace Microsoft.Toolkit.Extensions
{
if (column >= rectarray[r].Length)
{
yield return default(T);
yield return default;
continue;
}
@ -112,10 +48,28 @@ namespace Microsoft.Toolkit.Extensions
/// </summary>
/// <typeparam name="T">The element type of the array.</typeparam>
/// <param name="array">The source array.</param>
/// <returns>String representation of the array.</returns>
/// <returns>The <see cref="string"/> representation of the array.</returns>
public static string ToArrayString<T>(this T[] array)
{
return "[" + string.Join(",\t", array) + "]";
// The returned string will be in the following format:
// [1, 2, 3]
StringBuilder builder = new StringBuilder();
builder.Append('[');
for (int i = 0; i < array.Length; i++)
{
if (i != 0)
{
builder.Append(",\t");
}
builder.Append(array[i].ToString());
}
builder.Append(']');
return builder.ToString();
}
/// <summary>
@ -126,32 +80,89 @@ namespace Microsoft.Toolkit.Extensions
/// <returns>String representation of the array.</returns>
public static string ToArrayString<T>(this T[][] mdarray)
{
string[] inner = new string[mdarray.GetLength(0)];
// The returned string uses the same format as the overload for 2D arrays
StringBuilder builder = new StringBuilder();
for (int r = 0; r < mdarray.GetLength(0); r++)
builder.Append('[');
for (int i = 0; i < mdarray.Length; i++)
{
inner[r] = string.Join(",\t", mdarray[r]);
if (i != 0)
{
builder.Append(',');
builder.Append(Environment.NewLine);
builder.Append(' ');
}
builder.Append('[');
T[] row = mdarray[i];
for (int j = 0; j < row.Length; j++)
{
if (j != 0)
{
builder.Append(",\t");
}
builder.Append(row[j].ToString());
}
builder.Append(']');
}
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
builder.Append(']');
return builder.ToString();
}
/// <summary>
/// Returns a simple string representation of a rectangular array.
/// Returns a simple string representation of a 2D array.
/// </summary>
/// <typeparam name="T">The element type of the array.</typeparam>
/// <param name="rectarray">The source array.</param>
/// <returns>String representation of the array.</returns>
public static string ToArrayString<T>(this T[,] rectarray)
/// <param name="array">The source array.</param>
/// <returns>The <see cref="string"/> representation of the array.</returns>
public static string ToArrayString<T>(this T[,] array)
{
string[] inner = new string[rectarray.GetLength(0)];
// The returned string will be in the following format:
// [[1, 2, 3],
// [4, 5, 6],
// [7, 8, 9]]
StringBuilder builder = new StringBuilder();
for (int r = 0; r < rectarray.GetLength(0); r++)
builder.Append('[');
int
height = array.GetLength(0),
width = array.GetLength(1);
for (int i = 0; i < height; i++)
{
inner[r] = string.Join(",\t", rectarray.GetRow(r));
if (i != 0)
{
builder.Append(',');
builder.Append(Environment.NewLine);
builder.Append(' ');
}
builder.Append('[');
for (int j = 0; j < width; j++)
{
if (j != 0)
{
builder.Append(",\t");
}
builder.Append(array[i, j].ToString());
}
builder.Append(']');
}
return "[[" + string.Join("]," + Environment.NewLine + " [", inner) + "]]";
builder.Append(']');
return builder.ToString();
}
}
}

Просмотреть файл

@ -38,13 +38,13 @@ namespace UnitTests.HighPerformance.Buffers
Assert.AreEqual(size, pool.Size);
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool);
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!;
Assert.AreEqual(x, maps.Length);
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance");
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!;
int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(maps.GetValue(0));
int[] buckets = (int[])bucketType.GetField("buckets", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(maps.GetValue(0))!;
Assert.AreEqual(y, buckets.Length);
}
@ -64,7 +64,7 @@ namespace UnitTests.HighPerformance.Buffers
}
catch (ArgumentOutOfRangeException e)
{
var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) });
var cctor = typeof(StringPool).GetConstructor(new[] { typeof(int) })!;
Assert.AreEqual(cctor.GetParameters()[0].Name, e.ParamName);
}
@ -158,7 +158,7 @@ namespace UnitTests.HighPerformance.Buffers
[MethodImpl(MethodImplOptions.NoInlining)]
private static string ToStringNoInlining(object obj)
{
return obj.ToString();
return obj.ToString()!;
}
[TestCategory("StringPool")]
@ -285,15 +285,15 @@ namespace UnitTests.HighPerformance.Buffers
}
// Get the buckets
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(pool);
Array maps = (Array)typeof(StringPool).GetField("maps", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(pool)!;
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance");
FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic);
Type bucketType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap, Microsoft.Toolkit.HighPerformance")!;
FieldInfo timestampInfo = bucketType.GetField("timestamp", BindingFlags.Instance | BindingFlags.NonPublic)!;
// Force the timestamp to be the maximum value, or the test would take too long
for (int i = 0; i < maps.LongLength; i++)
{
object map = maps.GetValue(i);
object map = maps.GetValue(i)!;
timestampInfo.SetValue(map, uint.MaxValue);
@ -305,16 +305,16 @@ namespace UnitTests.HighPerformance.Buffers
_ = pool.GetOrAdd(text);
Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance");
Type heapEntryType = Type.GetType("Microsoft.Toolkit.HighPerformance.Buffers.StringPool+FixedSizePriorityMap+HeapEntry, Microsoft.Toolkit.HighPerformance")!;
foreach (var map in maps)
{
// Get the heap for each bucket
Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(map);
FieldInfo fieldInfo = heapEntryType.GetField("Timestamp");
Array heapEntries = (Array)bucketType.GetField("heapEntries", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(map)!;
FieldInfo fieldInfo = heapEntryType.GetField("Timestamp")!;
// Extract the array with the timestamps in the heap nodes
uint[] array = heapEntries.Cast<object>().Select(entry => (uint)fieldInfo.GetValue(entry)).ToArray();
uint[] array = heapEntries.Cast<object>().Select(entry => (uint)fieldInfo.GetValue(entry)!).ToArray();
static bool IsMinHeap(uint[] array)
{

Просмотреть файл

@ -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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -9,6 +10,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")]
public partial class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
@ -17,6 +19,12 @@ namespace UnitTests.HighPerformance.Extensions
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');
// In all these "DangerousGetReference" tests, we need to ensure that a reference to a given
// item within an array is effectively the one corresponding to the one whe expect, which is
// either a reference to the first item if we use "DangerousGetReference", or one to the n-th
// item if we use "DangerousGetReferenceAt". So all these tests just invoke the API and then
// compare the returned reference against an existing baseline (like the built-in array indexer)
// to ensure that the two are the same. These are all managed references, so no need for pinning.
ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
ref string r1 = ref Unsafe.AsRef(tokens[0]);

Просмотреть файл

@ -3,9 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
@ -23,6 +24,7 @@ namespace UnitTests.HighPerformance.Extensions
{ 9, 10, 11, 12 }
};
// See comments in Test_ArrayExtensions.1D for how these tests work
ref int r0 = ref array.DangerousGetReference();
ref int r1 = ref array[0, 0];
@ -81,11 +83,16 @@ namespace UnitTests.HighPerformance.Extensions
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayMid()
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayMid()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 1, 3, 2);
// To fill an array we now go through the Span2D<T> type, which includes all
// the necessary logic to perform the operation. In these tests we just create
// one through the extension, slice it and then fill it. For instance in this
// one, we're creating a Span2D<bool> from coordinates (1, 1), with a height of
// 2 and a width of 2, and then filling it. Then we just compare the results.
test.AsSpan2D(1, 1, 2, 3).Fill(true);
var expected = new[,]
{
@ -100,12 +107,12 @@ namespace UnitTests.HighPerformance.Extensions
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayTwice()
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayTwice()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 0, 0, 1, 2);
test.Fill(true, 1, 3, 2, 2);
test.AsSpan2D(0, 0, 2, 1).Fill(true);
test.AsSpan2D(1, 3, 2, 2).Fill(true);
var expected = new[,]
{
@ -120,30 +127,11 @@ namespace UnitTests.HighPerformance.Extensions
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayNegativeSize()
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomEdgeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 3, 4, -3, -2);
var expected = new[,]
{
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayBottomEdgeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 2, 2, 4);
test.AsSpan2D(1, 2, 3, 2).Fill(true);
var expected = new[,]
{
@ -158,30 +146,11 @@ namespace UnitTests.HighPerformance.Extensions
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayTopLeftCornerNegativeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, -1, -1, 3, 3);
var expected = new[,]
{
{ true, true, false, false, false },
{ true, true, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayBottomRightCornerBoundary()
public void Test_ArrayExtensions_2D_AsSpan2DAndFillArrayBottomRightCornerBoundary()
{
bool[,] test = new bool[5, 4];
test.Fill(true, 3, 2, 3, 3);
test.AsSpan2D(3, 2, 2, 2).Fill(true);
var expected = new[,]
{
@ -197,8 +166,6 @@ namespace UnitTests.HighPerformance.Extensions
[TestCategory("ArrayExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")]
public void Test_ArrayExtensions_2D_GetRow_Rectangle()
{
int[,] array =
@ -208,38 +175,29 @@ namespace UnitTests.HighPerformance.Extensions
{ 9, 10, 11, 12 }
};
// Here we use the enumerator on the RefEnumerator<T> type to traverse items in a row
// by reference. For each one, we check that the reference does in fact point to the
// item we expect in the underlying array (in this case, items on row 1).
int j = 0;
foreach (ref int value in array.GetRow(1))
{
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++]));
}
// Check that RefEnumerable<T>.ToArray() works correctly
CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 });
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetRow(-1)) { }
});
// Test an empty array
Assert.AreSame(new int[1, 0].GetRow(0).ToArray(), Array.Empty<int>());
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetRow(20)) { }
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(20));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_GetRow_Empty()
{
int[,] array = new int[0, 0];
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(0).ToArray());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312", Justification = "Dummy loop variable")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1501", Justification = "Empty test loop")]
public void Test_ArrayExtensions_2D_GetColumn_Rectangle()
{
int[,] array =
@ -249,6 +207,7 @@ namespace UnitTests.HighPerformance.Extensions
{ 9, 10, 11, 12 }
};
// Same as above, but this time we iterate a column instead (so non contiguous items)
int i = 0;
foreach (ref int value in array.GetColumn(1))
{
@ -257,15 +216,215 @@ namespace UnitTests.HighPerformance.Extensions
CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 });
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetColumn(-1)) { }
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(4));
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(20));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_GetRow_Empty()
{
int[,] array = new int[0, 0];
// Try to get a row from an empty array (the row index isn't in range)
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetRow(0).ToArray());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_GetRowOrColumn_Helpers()
{
int[,] array =
{
foreach (var _ in array.GetColumn(20)) { }
});
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
// Get a row and test the Clear method. Note that the Span2D<T> here is sliced
// starting from the second column, so this method should clear the row from index 1.
array.AsSpan2D(1, 1, 3, 3).GetRow(0).Clear();
int[,] expected =
{
{ 1, 2, 3, 4 },
{ 5, 0, 0, 0 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
CollectionAssert.AreEqual(array, expected);
// Same as before, but this time we fill a column with a value
array.GetColumn(2).Fill(42);
expected = new[,]
{
{ 1, 2, 42, 4 },
{ 5, 0, 42, 0 },
{ 9, 10, 42, 12 },
{ 13, 14, 42, 16 }
};
CollectionAssert.AreEqual(array, expected);
int[] copy = new int[4];
// Get a row and copy items to a target span (in this case, wrapping an array)
array.GetRow(2).CopyTo(copy);
int[] result = { 9, 10, 42, 12 };
CollectionAssert.AreEqual(copy, result);
// Same as above, but copying from a column (so we test non contiguous sequences too)
array.GetColumn(1).CopyTo(copy);
result = new[] { 2, 0, 10, 14 };
CollectionAssert.AreEqual(copy, result);
// Some invalid attempts to copy to an empty span or sequence
Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(RefEnumerable<int>)));
Assert.ThrowsException<ArgumentException>(() => array.GetRow(0).CopyTo(default(Span<int>)));
Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(RefEnumerable<int>)));
Assert.ThrowsException<ArgumentException>(() => array.GetColumn(0).CopyTo(default(Span<int>)));
// Same as CopyTo, but this will fail gracefully with an invalid target
Assert.IsTrue(array.GetRow(2).TryCopyTo(copy));
Assert.IsFalse(array.GetRow(0).TryCopyTo(default(Span<int>)));
result = new[] { 9, 10, 42, 12 };
CollectionAssert.AreEqual(copy, result);
// Also fill a row and then further down clear a column (trying out all possible combinations)
array.GetRow(2).Fill(99);
expected = new[,]
{
{ 1, 2, 42, 4 },
{ 5, 0, 42, 0 },
{ 99, 99, 99, 99 },
{ 13, 14, 42, 16 }
};
CollectionAssert.AreEqual(array, expected);
array.GetColumn(2).Clear();
expected = new[,]
{
{ 1, 2, 0, 4 },
{ 5, 0, 0, 0 },
{ 99, 99, 0, 99 },
{ 13, 14, 0, 16 }
};
CollectionAssert.AreEqual(array, expected);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_ReadOnlyGetRowOrColumn_Helpers()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
// This test pretty much does the same things as the method above, but this time
// using a source ReadOnlySpan2D<T>, so that the sequence type being tested is
// ReadOnlyRefEnumerable<T> instead (which shares most features but is separate).
ReadOnlySpan2D<int> span2D = array;
int[] copy = new int[4];
span2D.GetRow(2).CopyTo(copy);
int[] result = { 9, 10, 11, 12 };
CollectionAssert.AreEqual(copy, result);
span2D.GetColumn(1).CopyTo(copy);
result = new[] { 2, 6, 10, 14 };
CollectionAssert.AreEqual(copy, result);
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(RefEnumerable<int>)));
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetRow(0).CopyTo(default(Span<int>)));
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(RefEnumerable<int>)));
Assert.ThrowsException<ArgumentException>(() => ((ReadOnlySpan2D<int>)array).GetColumn(0).CopyTo(default(Span<int>)));
Assert.IsTrue(span2D.GetRow(2).TryCopyTo(copy));
Assert.IsFalse(span2D.GetRow(2).TryCopyTo(default(Span<int>)));
result = new[] { 9, 10, 11, 12 };
CollectionAssert.AreEqual(copy, result);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_RefEnumerable_Misc()
{
int[,] array1 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
int[,] array2 = new int[4, 4];
// Copy to enumerable with source step == 1, destination step == 1
array1.GetRow(0).CopyTo(array2.GetRow(0));
// Copy enumerable with source step == 1, destination step != 1
array1.GetRow(1).CopyTo(array2.GetColumn(1));
// Copy enumerable with source step != 1, destination step == 1
array1.GetColumn(2).CopyTo(array2.GetRow(2));
// Copy enumerable with source step != 1, destination step != 1
array1.GetColumn(3).CopyTo(array2.GetColumn(3));
int[,] result =
{
{ 1, 5, 3, 4 },
{ 0, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 0, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
// Test a valid and an invalid TryCopyTo call with the RefEnumerable<T> overload
bool shouldBeTrue = array1.GetRow(0).TryCopyTo(array2.GetColumn(0));
bool shouldBeFalse = array1.GetRow(0).TryCopyTo(default(RefEnumerable<int>));
result = new[,]
{
{ 1, 5, 3, 4 },
{ 2, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 4, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
Assert.IsTrue(shouldBeTrue);
Assert.IsFalse(shouldBeFalse);
}
[TestCategory("ArrayExtensions")]
@ -286,6 +445,7 @@ namespace UnitTests.HighPerformance.Extensions
Span<int> span = array.AsSpan();
// Check that the empty array was loaded properly
Assert.AreEqual(span.Length, array.Length);
Assert.IsTrue(span.IsEmpty);
}
@ -303,11 +463,14 @@ namespace UnitTests.HighPerformance.Extensions
Span<int> span = array.AsSpan();
// Test the total length of the span
Assert.AreEqual(span.Length, array.Length);
ref int r0 = ref array[0, 0];
ref int r1 = ref span[0];
// Similarly to the top methods, here we compare a given reference to
// ensure they point to the right element back in the original array.
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
#endif

Просмотреть файл

@ -0,0 +1,62 @@
// 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;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
public partial class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_3D_DangerousGetReference_Int()
{
int[,,] array = new int[10, 20, 12];
// See comments in Test_ArrayExtensions.1D for how these tests work
ref int r0 = ref array.DangerousGetReference();
ref int r1 = ref array[0, 0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_3D_DangerousGetReference_String()
{
string[,,] array = new string[10, 20, 12];
ref string r0 = ref array.DangerousGetReference();
ref string r1 = ref array[0, 0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Zero()
{
int[,,] array = new int[10, 20, 12];
ref int r0 = ref array.DangerousGetReferenceAt(0, 0, 0);
ref int r1 = ref array[0, 0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_3D_DangerousGetReferenceAt_Index()
{
int[,,] array = new int[10, 20, 12];
ref int r0 = ref array.DangerousGetReferenceAt(5, 3, 4);
ref int r1 = ref array[5, 3, 4];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
}
}

Просмотреть файл

@ -15,16 +15,18 @@ namespace UnitTests.HighPerformance.Extensions
[TestMethod]
public void Test_BoolExtensions_True()
{
Assert.AreEqual(1, true.ToInt(), nameof(Test_BoolExtensions_True));
Assert.AreEqual(1, (DateTime.Now.Year > 0).ToInt(), nameof(Test_BoolExtensions_True));
// There tests all just run a couple of boolean expressions and validate that the extension
// correctly produces either 1 or 0 depending on whether the expression was true or false.
Assert.AreEqual(1, true.ToByte(), nameof(Test_BoolExtensions_True));
Assert.AreEqual(1, (DateTime.Now.Year > 0).ToByte(), nameof(Test_BoolExtensions_True));
}
[TestCategory("BoolExtensions")]
[TestMethod]
public void Test_BoolExtensions_False()
{
Assert.AreEqual(0, false.ToInt(), nameof(Test_BoolExtensions_False));
Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToInt(), nameof(Test_BoolExtensions_False));
Assert.AreEqual(0, false.ToByte(), nameof(Test_BoolExtensions_False));
Assert.AreEqual(0, (DateTime.Now.Year > 3000).ToByte(), nameof(Test_BoolExtensions_False));
}
[TestCategory("BoolExtensions")]

Просмотреть файл

@ -103,7 +103,7 @@ namespace UnitTests.HighPerformance.Extensions
public Int(int value) => Value = value;
public bool Equals(Int other)
public bool Equals(Int? other)
{
if (other is null)
{
@ -118,7 +118,7 @@ namespace UnitTests.HighPerformance.Extensions
return this.Value == other.Value;
}
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || (obj is Int other && Equals(other));
}

Просмотреть файл

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
@ -12,6 +13,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601", Justification = "Partial test class")]
public partial class Test_ReadOnlySpanExtensions
{
[TestCategory("ReadOnlySpanExtensions")]
@ -267,5 +269,47 @@ namespace UnitTests.HighPerformance.Extensions
CollectionAssert.AreEqual(result, tokens);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_CopyTo_RefEnumerable()
{
int[,] array = new int[4, 5];
ReadOnlySpan<int>
values1 = new[] { 10, 20, 30, 40, 50 },
values2 = new[] { 11, 22, 33, 44, 55 };
// Copy a span to a target row and column with valid lengths
values1.CopyTo(array.GetRow(0));
values2.Slice(0, 4).CopyTo(array.GetColumn(1));
int[,] result =
{
{ 10, 11, 30, 40, 50 },
{ 0, 22, 0, 0, 0 },
{ 0, 33, 0, 0, 0 },
{ 0, 44, 0, 0, 0 }
};
CollectionAssert.AreEqual(array, result);
// Try to copy to a valid row and an invalid column (too short for the source span)
bool shouldBeTrue = values1.TryCopyTo(array.GetRow(2));
bool shouldBeFalse = values2.TryCopyTo(array.GetColumn(3));
Assert.IsTrue(shouldBeTrue);
Assert.IsFalse(shouldBeFalse);
result = new[,]
{
{ 10, 11, 30, 40, 50 },
{ 0, 22, 0, 0, 0 },
{ 10, 20, 30, 40, 50 },
{ 0, 44, 0, 0, 0 }
};
CollectionAssert.AreEqual(array, result);
}
}
}

Просмотреть файл

@ -168,5 +168,47 @@ namespace UnitTests.HighPerformance.Extensions
Assert.Fail("Empty source sequence");
}
}
[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_CopyTo_RefEnumerable()
{
int[,] array = new int[4, 5];
int[]
values1 = { 10, 20, 30, 40, 50 },
values2 = { 11, 22, 33, 44, 55 };
// Copy a span to a target row and column with valid lengths
values1.AsSpan().CopyTo(array.GetRow(0));
values2.AsSpan(0, 4).CopyTo(array.GetColumn(1));
int[,] result =
{
{ 10, 11, 30, 40, 50 },
{ 0, 22, 0, 0, 0 },
{ 0, 33, 0, 0, 0 },
{ 0, 44, 0, 0, 0 }
};
CollectionAssert.AreEqual(array, result);
// Try to copy to a valid row and an invalid column (too short for the source span)
bool shouldBeTrue = values1.AsSpan().TryCopyTo(array.GetRow(2));
bool shouldBeFalse = values2.AsSpan().TryCopyTo(array.GetColumn(3));
Assert.IsTrue(shouldBeTrue);
Assert.IsFalse(shouldBeFalse);
result = new[,]
{
{ 10, 11, 30, 40, 50 },
{ 0, 22, 0, 0, 0 },
{ 10, 20, 30, 40, 50 },
{ 0, 44, 0, 0, 0 }
};
CollectionAssert.AreEqual(array, result);
}
}
}

Просмотреть файл

@ -63,7 +63,6 @@ namespace UnitTests.HighPerformance.Helpers
TestForType<char>();
}
#if NETCOREAPP3_1
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_ManagedType_TestRepeat()
@ -89,7 +88,6 @@ namespace UnitTests.HighPerformance.Helpers
Assert.AreEqual(hash1, hash2, $"Failed {typeof(string)} test with count {count}: got {hash1} and then {hash2}");
}
}
#endif
/// <summary>
/// Performs a test for a specified type.

Просмотреть файл

@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
[TestCategory("ParallelHelper")]
[TestMethod]
[DataRow(1, 1, 0, 0, 1, 1)]
[DataRow(1, 2, 0, 0, 1, 2)]
[DataRow(2, 3, 0, 0, 2, 3)]
[DataRow(2, 3, 0, 1, 2, 2)]
[DataRow(3, 3, 1, 1, 2, 2)]
[DataRow(12, 12, 2, 4, 3, 3)]
[DataRow(64, 64, 0, 0, 32, 32)]
[DataRow(64, 64, 13, 14, 23, 22)]
public unsafe void Test_ParallelHelper_ForEach_In2D(
int sizeY,
int sizeX,
int row,
int column,
int height,
int width)
{
int[,] data = CreateRandomData2D(sizeY, sizeX);
// Create a memory wrapping the random array with the given parameters
ReadOnlyMemory2D<int> memory = data.AsMemory2D(row, column, height, width);
Assert.AreEqual(memory.Length, height * width);
Assert.AreEqual(memory.Height, height);
Assert.AreEqual(memory.Width, width);
int sum = 0;
// Sum all the items in parallel. The Summer type takes a pointer to a target value
// and adds values to it in a thread-safe manner (with an interlocked add).
ParallelHelper.ForEach(memory, new Summer(&sum));
int expected = 0;
// Calculate the sum iteratively as a baseline for comparison
foreach (int n in memory.Span)
{
expected += n;
}
Assert.AreEqual(sum, expected, $"The sum doesn't match, was {sum} instead of {expected}");
}
/// <summary>
/// Creates a random 2D <see cref="int"/> array filled with random numbers.
/// </summary>
/// <param name="height">The height of the array to create.</param>
/// <param name="width">The width of the array to create.</param>
/// <returns>An array of random <see cref="int"/> elements.</returns>
[Pure]
private static int[,] CreateRandomData2D(int height, int width)
{
var random = new Random((height * 33) + width);
int[,] data = new int[height, width];
foreach (ref int n in data.AsSpan2D())
{
n = random.Next(0, byte.MaxValue);
}
return data;
}
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// 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 Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
[TestCategory("ParallelHelper")]
[TestMethod]
[DataRow(1, 1, 0, 0, 1, 1)]
[DataRow(1, 2, 0, 0, 1, 2)]
[DataRow(2, 3, 0, 0, 2, 3)]
[DataRow(2, 3, 0, 1, 2, 2)]
[DataRow(3, 3, 1, 1, 2, 2)]
[DataRow(12, 12, 2, 4, 3, 3)]
[DataRow(64, 64, 0, 0, 32, 32)]
[DataRow(64, 64, 13, 14, 23, 22)]
public void Test_ParallelHelper_ForEach_Ref2D(
int sizeY,
int sizeX,
int row,
int column,
int height,
int width)
{
int[,]
data = CreateRandomData2D(sizeY, sizeX),
copy = (int[,])data.Clone();
// Prepare the target data iteratively
foreach (ref int n in copy.AsSpan2D(row, column, height, width))
{
n = unchecked(n * 397);
}
Memory2D<int> memory = data.AsMemory2D(row, column, height, width);
Assert.AreEqual(memory.Length, height * width);
Assert.AreEqual(memory.Height, height);
Assert.AreEqual(memory.Width, width);
// Do the same computation in paralellel, then compare the two arrays
ParallelHelper.ForEach(memory, new Multiplier(397));
CollectionAssert.AreEqual(data, copy);
}
}
}

Просмотреть файл

@ -0,0 +1,544 @@
// 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.CodeAnalysis;
#if !WINDOWS_UWP
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
#endif
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Memory
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_Memory2DT
{
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Empty()
{
// Create a few empty Memory2D<T> instances in different ways and
// check to ensure the right parameters were used to initialize them.
Memory2D<int> empty1 = default;
Assert.IsTrue(empty1.IsEmpty);
Assert.AreEqual(empty1.Length, 0);
Assert.AreEqual(empty1.Width, 0);
Assert.AreEqual(empty1.Height, 0);
Memory2D<string> empty2 = Memory2D<string>.Empty;
Assert.IsTrue(empty2.IsEmpty);
Assert.AreEqual(empty2.Length, 0);
Assert.AreEqual(empty2.Width, 0);
Assert.AreEqual(empty2.Height, 0);
Memory2D<int> empty3 = new int[4, 0];
Assert.IsTrue(empty3.IsEmpty);
Assert.AreEqual(empty3.Length, 0);
Assert.AreEqual(empty3.Width, 0);
Assert.AreEqual(empty3.Height, 4);
Memory2D<int> empty4 = new int[0, 7];
Assert.IsTrue(empty4.IsEmpty);
Assert.AreEqual(empty4.Length, 0);
Assert.AreEqual(empty4.Width, 7);
Assert.AreEqual(empty4.Height, 0);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Array1DConstructor()
{
int[] array =
{
1, 2, 3, 4, 5, 6
};
// Create a memory over a 1D array with 2D data in row-major order. This tests
// the T[] array constructor for Memory2D<T> with custom size and pitch.
Memory2D<int> memory2d = new Memory2D<int>(array, 1, 2, 2, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
// Also ensure the right exceptions are thrown with invalid parameters, such as
// negative indices, indices out of range, values that are too big, etc.
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1], 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -99, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, -10, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 1, -100, 1));
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 2, 4, 0));
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 3, 3, 0));
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 1, 2, 3, 0));
Assert.ThrowsException<ArgumentException>(() => new Memory2D<int>(array, 0, 10, 1, 120));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Array2DConstructor_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// Test the constructor taking a T[,] array that is mapped directly (no slicing)
Memory2D<int> memory2d = new Memory2D<int>(array);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 6);
Assert.AreEqual(memory2d.Width, 3);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 1], 2);
Assert.AreEqual(memory2d.Span[1, 2], 6);
// Here we test the check for covariance: we can't create a Memory2D<T> from a U[,] array
// where U is assignable to T (as in, U : T). This would cause a type safety violation on write.
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1, 2]));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Array2DConstructor_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// Same as above, but this time we also slice the memory to test the other constructor
Memory2D<int> memory2d = new Memory2D<int>(array, 0, 1, 2, 2);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
Assert.ThrowsException<ArrayTypeMismatchException>(() => new Memory2D<object>(new string[1, 2], 0, 0, 2, 2));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Array3DConstructor_1()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
// Same as above, but we test the constructor taking a layer within a 3D array
Memory2D<int> memory2d = new Memory2D<int>(array, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 6);
Assert.AreEqual(memory2d.Width, 3);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 1], 20);
Assert.AreEqual(memory2d.Span[1, 2], 60);
// A couple of tests for invalid parameters, ie. layers out of range
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 2));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 20));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Array3DConstructor_2()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
// Same as above, but we also slice the target layer in the 3D array. In this case we're creating
// a Memory<int> instance from a slice in the layer at depth 1 in our 3D array, and with an area
// starting at coorsinates (0, 1), with a height of 2 and width of 2. So we want to wrap the
// square with items [20, 30, 50, 60] in the second layer of the 3D array above.
Memory2D<int> memory2d = new Memory2D<int>(array, 1, 0, 1, 2, 2);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 20);
Assert.AreEqual(memory2d.Span[1, 1], 60);
// Same as above, testing a few cases with invalid parameters
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, -1, 1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, -1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 1, 1, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 2, 0, 0, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 1, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 0, 2, 4));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array, 0, 0, 0, 3, 3));
}
#if !WINDOWS_UWP
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_MemoryConstructor()
{
Memory<int> memory = new[]
{
1, 2, 3, 4, 5, 6
};
// We also test the constructor that takes an input Memory<T> instance.
// This is only available on runtimes with fast Span<T> support, as otherwise
// the implementation would be too complex and slow to work in this case.
// Conceptually, this works the same as when wrapping a 1D array with row-major items.
Memory2D<int> memory2d = memory.AsMemory2D(1, 2, 2, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(-99, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, -10, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, -100, 1));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 2, 4, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 3, 3, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(1, 2, 3, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 10, 1, 120));
}
#endif
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Slice_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
Memory2D<int> memory2d = new Memory2D<int>(array);
// Test a slice from a Memory2D<T> with valid parameters
Memory2D<int> slice1 = memory2d.Slice(1, 1, 1, 2);
Assert.AreEqual(slice1.Length, 2);
Assert.AreEqual(slice1.Height, 1);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1.Span[0, 0], 5);
Assert.AreEqual(slice1.Span[0, 1], 6);
// Same above, but we test slicing a pre-sliced instance as well. This
// is done to verify that the internal offsets are properly tracked
// across multiple slicing operations, instead of just in the first.
Memory2D<int> slice2 = memory2d.Slice(0, 1, 2, 2);
Assert.AreEqual(slice2.Length, 4);
Assert.AreEqual(slice2.Height, 2);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2.Span[0, 0], 2);
Assert.AreEqual(slice2.Span[1, 0], 5);
Assert.AreEqual(slice2.Span[1, 1], 6);
// A few invalid slicing operations, with out of range parameters
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(-1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(10, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 12, 1, 12));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 1, 55, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 0, 2, 4));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 0, 3, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(0, 1, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new Memory2D<int>(array).Slice(1, 0, 2, 3));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Slice_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
Memory2D<int> memory2d = new Memory2D<int>(array);
// Mostly the same test as above, just with different parameters
Memory2D<int> slice1 = memory2d.Slice(0, 0, 2, 2);
Assert.AreEqual(slice1.Length, 4);
Assert.AreEqual(slice1.Height, 2);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1.Span[0, 0], 1);
Assert.AreEqual(slice1.Span[1, 1], 5);
Memory2D<int> slice2 = slice1.Slice(1, 0, 1, 2);
Assert.AreEqual(slice2.Length, 2);
Assert.AreEqual(slice2.Height, 1);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2.Span[0, 0], 4);
Assert.AreEqual(slice2.Span[0, 1], 5);
Memory2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
Assert.AreEqual(slice3.Length, 1);
Assert.AreEqual(slice3.Height, 1);
Assert.AreEqual(slice3.Width, 1);
Assert.AreEqual(slice3.Span[0, 0], 5);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_TryGetMemory_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
Memory2D<int> memory2d = new Memory2D<int>(array);
// Here we test that we can get a Memory<T> from a 2D one when the underlying
// data is contiguous. Note that in this case this can only work on runtimes
// with fast Span<T> support, because otherwise it's not possible to get a
// Memory<T> (or a Span<T> too, for that matter) from a 2D array.
bool success = memory2d.TryGetMemory(out Memory<int> memory);
#if WINDOWS_UWP
Assert.IsFalse(success);
Assert.IsTrue(memory.IsEmpty);
#else
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, array.Length);
Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref memory.Span[0]));
#endif
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_TryGetMemory_2()
{
int[] array = { 1, 2, 3, 4 };
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
// Same test as above, but this will always succeed on all runtimes,
// as creating a Memory<T> from a 1D array is always supported.
bool success = memory2d.TryGetMemory(out Memory<int> memory);
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, array.Length);
Assert.AreEqual(memory.Span[2], 3);
}
#if !WINDOWS_UWP
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_TryGetMemory_3()
{
Memory<int> data = new[] { 1, 2, 3, 4 };
Memory2D<int> memory2d = data.AsMemory2D(2, 2);
// Same as above, just with the extra Memory<T> indirection. Same as above,
// this test is only supported on runtimes with fast Span<T> support.
// On others, we just don't expose the Memory<T>.AsMemory2D extension.
bool success = memory2d.TryGetMemory(out Memory<int> memory);
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, data.Length);
Assert.AreEqual(memory.Span[2], 3);
}
#endif
[TestCategory("Memory2DT")]
[TestMethod]
public unsafe void Test_Memory2DT_Pin_1()
{
int[] array = { 1, 2, 3, 4 };
// We create a Memory2D<T> from an array and verify that pinning this
// instance correctly returns a pointer to the right array element.
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
using var pin = memory2d.Pin();
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
}
[TestCategory("Memory2DT")]
[TestMethod]
public unsafe void Test_Memory2DT_Pin_2()
{
int[] array = { 1, 2, 3, 4 };
// Same as above, but we test with a sliced Memory2D<T> instance
Memory2D<int> memory2d = new Memory2D<int>(array, 2, 2);
using var pin = memory2d.Pin();
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_ToArray_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// Here we create a Memory2D<T> instance from a 2D array and then verify that
// calling ToArray() creates an array that matches the contents of the first.
Memory2D<int> memory2d = new Memory2D<int>(array);
int[,] copy = memory2d.ToArray();
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
CollectionAssert.AreEqual(array, copy);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_ToArray_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// Same as above, but with a sliced Memory2D<T> instance
Memory2D<int> memory2d = new Memory2D<int>(array, 0, 0, 2, 2);
int[,] copy = memory2d.ToArray();
Assert.AreEqual(copy.GetLength(0), 2);
Assert.AreEqual(copy.GetLength(1), 2);
int[,] expected =
{
{ 1, 2 },
{ 4, 5 }
};
CollectionAssert.AreEqual(expected, copy);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_Equals()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
// Here we want to verify that the Memory2D<T>.Equals method works correctly. This is true
// when the wrapped instance is the same, and the various internal offsets and sizes match.
Memory2D<int> memory2d = new Memory2D<int>(array);
Assert.IsFalse(memory2d.Equals(null));
Assert.IsFalse(memory2d.Equals(new Memory2D<int>(array, 0, 1, 2, 2)));
Assert.IsTrue(memory2d.Equals(new Memory2D<int>(array)));
Assert.IsTrue(memory2d.Equals(memory2d));
// This should work also when casting to a ReadOnlyMemory2D<T> instance
ReadOnlyMemory2D<int> readOnlyMemory2d = memory2d;
Assert.IsTrue(memory2d.Equals(readOnlyMemory2d));
Assert.IsFalse(memory2d.Equals(readOnlyMemory2d.Slice(0, 1, 2, 2)));
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_GetHashCode()
{
// An emoty Memory2D<T> has just 0 as the hashcode
Assert.AreEqual(Memory2D<int>.Empty.GetHashCode(), 0);
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
Memory2D<int> memory2d = new Memory2D<int>(array);
// Ensure that the GetHashCode method is repeatable
int a = memory2d.GetHashCode(), b = memory2d.GetHashCode();
Assert.AreEqual(a, b);
// The hashcode shouldn't match when the size is different
int c = new Memory2D<int>(array, 0, 1, 2, 2).GetHashCode();
Assert.AreNotEqual(a, c);
}
[TestCategory("Memory2DT")]
[TestMethod]
public void Test_Memory2DT_ToString()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
Memory2D<int> memory2d = new Memory2D<int>(array);
// Here we just want to verify that the type is nicely printed as expected, along with the size
string text = memory2d.ToString();
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.Memory2D<System.Int32>[2, 3]";
Assert.AreEqual(text, expected);
}
}
}

Просмотреть файл

@ -0,0 +1,492 @@
// 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.CodeAnalysis;
#if !WINDOWS_UWP
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
#endif
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Memory
{
/* ====================================================================
* NOTE
* ====================================================================
* All the tests here mirror the ones for Memory2D<T>, as the two types
* are basically the same except for some small differences in return types
* or some checks being done upon construction. See comments in the test
* file for Memory2D<T> for more info on these tests. */
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_ReadOnlyMemory2DT
{
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Empty()
{
ReadOnlyMemory2D<int> empty1 = default;
Assert.IsTrue(empty1.IsEmpty);
Assert.AreEqual(empty1.Length, 0);
Assert.AreEqual(empty1.Width, 0);
Assert.AreEqual(empty1.Height, 0);
ReadOnlyMemory2D<string> empty2 = ReadOnlyMemory2D<string>.Empty;
Assert.IsTrue(empty2.IsEmpty);
Assert.AreEqual(empty2.Length, 0);
Assert.AreEqual(empty2.Width, 0);
Assert.AreEqual(empty2.Height, 0);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Array1DConstructor()
{
int[] array =
{
1, 2, 3, 4, 5, 6
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1, 2, 2, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
// Here we check to ensure a covariant array conversion is allowed for ReadOnlyMemory2D<T>
_ = new ReadOnlyMemory2D<object>(new string[1], 1, 1);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -99, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, -10, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 1, -100, 1));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 2, 4, 0));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 3, 3, 0));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 1, 2, 3, 0));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlyMemory2D<int>(array, 0, 10, 1, 120));
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Array2DConstructor_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 6);
Assert.AreEqual(memory2d.Width, 3);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 1], 2);
Assert.AreEqual(memory2d.Span[1, 2], 6);
_ = new ReadOnlyMemory2D<object>(new string[1, 2]);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Array2DConstructor_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
_ = new ReadOnlyMemory2D<object>(new string[1, 2]);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<object>(new string[1, 2], 0, 0, 2, 2));
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Array3DConstructor_1()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 6);
Assert.AreEqual(memory2d.Width, 3);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 1], 20);
Assert.AreEqual(memory2d.Span[1, 2], 60);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 20));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 2));
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Array3DConstructor_2()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 1, 0, 1, 2, 2);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 20);
Assert.AreEqual(memory2d.Span[1, 1], 60);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, -1, 1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, -1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 1, 1, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 2, 0, 0, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 1, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 0, 2, 4));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array, 0, 0, 0, 3, 3));
}
#if !WINDOWS_UWP
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_ReadOnlyMemoryConstructor()
{
ReadOnlyMemory<int> memory = new[]
{
1, 2, 3, 4, 5, 6
};
ReadOnlyMemory2D<int> memory2d = memory.AsMemory2D(1, 2, 2, 1);
Assert.IsFalse(memory2d.IsEmpty);
Assert.AreEqual(memory2d.Length, 4);
Assert.AreEqual(memory2d.Width, 2);
Assert.AreEqual(memory2d.Height, 2);
Assert.AreEqual(memory2d.Span[0, 0], 2);
Assert.AreEqual(memory2d.Span[1, 1], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(-99, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, -10, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => memory.AsMemory2D(0, 1, -100, 1));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 2, 4, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 3, 3, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(1, 2, 3, 0));
Assert.ThrowsException<ArgumentException>(() => memory.AsMemory2D(0, 10, 1, 120));
}
#endif
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Slice_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
ReadOnlyMemory2D<int> slice1 = memory2d.Slice(1, 1, 1, 2);
Assert.AreEqual(slice1.Length, 2);
Assert.AreEqual(slice1.Height, 1);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1.Span[0, 0], 5);
Assert.AreEqual(slice1.Span[0, 1], 6);
ReadOnlyMemory2D<int> slice2 = memory2d.Slice(0, 1, 2, 2);
Assert.AreEqual(slice2.Length, 4);
Assert.AreEqual(slice2.Height, 2);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2.Span[0, 0], 2);
Assert.AreEqual(slice2.Span[1, 0], 5);
Assert.AreEqual(slice2.Span[1, 1], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(-1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(10, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 12, 1, 12));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 1, 55, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 0, 2, 4));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 0, 3, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(0, 1, 2, 3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlyMemory2D<int>(array).Slice(1, 0, 2, 3));
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Slice_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
ReadOnlyMemory2D<int> slice1 = memory2d.Slice(0, 0, 2, 2);
Assert.AreEqual(slice1.Length, 4);
Assert.AreEqual(slice1.Height, 2);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1.Span[0, 0], 1);
Assert.AreEqual(slice1.Span[1, 1], 5);
ReadOnlyMemory2D<int> slice2 = slice1.Slice(1, 0, 1, 2);
Assert.AreEqual(slice2.Length, 2);
Assert.AreEqual(slice2.Height, 1);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2.Span[0, 0], 4);
Assert.AreEqual(slice2.Span[0, 1], 5);
ReadOnlyMemory2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
Assert.AreEqual(slice3.Length, 1);
Assert.AreEqual(slice3.Height, 1);
Assert.AreEqual(slice3.Width, 1);
Assert.AreEqual(slice3.Span[0, 0], 5);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
#if WINDOWS_UWP
Assert.IsFalse(success);
Assert.IsTrue(memory.IsEmpty);
#else
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, array.Length);
Assert.IsTrue(Unsafe.AreSame(ref array[0, 0], ref Unsafe.AsRef(memory.Span[0])));
#endif
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_2()
{
int[] array = { 1, 2, 3, 4 };
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, array.Length);
Assert.AreEqual(memory.Span[2], 3);
}
#if !WINDOWS_UWP
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_TryGetReadOnlyMemory_3()
{
ReadOnlyMemory<int> data = new[] { 1, 2, 3, 4 };
ReadOnlyMemory2D<int> memory2d = data.AsMemory2D(2, 2);
bool success = memory2d.TryGetMemory(out ReadOnlyMemory<int> memory);
Assert.IsTrue(success);
Assert.AreEqual(memory.Length, data.Length);
Assert.AreEqual(memory.Span[2], 3);
}
#endif
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public unsafe void Test_ReadOnlyMemory2DT_Pin_1()
{
int[] array = { 1, 2, 3, 4 };
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
using var pin = memory2d.Pin();
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public unsafe void Test_ReadOnlyMemory2DT_Pin_2()
{
int[] array = { 1, 2, 3, 4 };
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 2, 2);
using var pin = memory2d.Pin();
Assert.AreEqual(((int*)pin.Pointer)[0], 1);
Assert.AreEqual(((int*)pin.Pointer)[3], 4);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_ToArray_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
int[,] copy = memory2d.ToArray();
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
CollectionAssert.AreEqual(array, copy);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_ToArray_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array, 0, 0, 2, 2);
int[,] copy = memory2d.ToArray();
Assert.AreEqual(copy.GetLength(0), 2);
Assert.AreEqual(copy.GetLength(1), 2);
int[,] expected =
{
{ 1, 2 },
{ 4, 5 }
};
CollectionAssert.AreEqual(expected, copy);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_Equals()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> readOnlyMemory2D = new ReadOnlyMemory2D<int>(array);
Assert.IsFalse(readOnlyMemory2D.Equals(null));
Assert.IsFalse(readOnlyMemory2D.Equals(new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2)));
Assert.IsTrue(readOnlyMemory2D.Equals(new ReadOnlyMemory2D<int>(array)));
Assert.IsTrue(readOnlyMemory2D.Equals(readOnlyMemory2D));
Memory2D<int> memory2d = array;
Assert.IsTrue(readOnlyMemory2D.Equals((object)memory2d));
Assert.IsFalse(readOnlyMemory2D.Equals((object)memory2d.Slice(0, 1, 2, 2)));
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_GetHashCode()
{
Assert.AreEqual(ReadOnlyMemory2D<int>.Empty.GetHashCode(), 0);
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
int a = memory2d.GetHashCode(), b = memory2d.GetHashCode();
Assert.AreEqual(a, b);
int c = new ReadOnlyMemory2D<int>(array, 0, 1, 2, 2).GetHashCode();
Assert.AreNotEqual(a, c);
}
[TestCategory("ReadOnlyMemory2DT")]
[TestMethod]
public void Test_ReadOnlyMemory2DT_ToString()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlyMemory2D<int> memory2d = new ReadOnlyMemory2D<int>(array);
string text = memory2d.ToString();
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<System.Int32>[2, 3]";
Assert.AreEqual(text, expected);
}
}
}

Просмотреть файл

@ -0,0 +1,924 @@
// 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.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Memory
{
/* ====================================================================
* NOTE
* ====================================================================
* All the tests here mirror the ones for ReadOnlySpan2D<T>. See comments
* in the test file for Span2D<T> for more info on these tests. */
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_ReadOnlySpan2DT
{
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Empty()
{
ReadOnlySpan2D<int> empty1 = default;
Assert.IsTrue(empty1.IsEmpty);
Assert.AreEqual(empty1.Length, 0);
Assert.AreEqual(empty1.Width, 0);
Assert.AreEqual(empty1.Height, 0);
ReadOnlySpan2D<string> empty2 = ReadOnlySpan2D<string>.Empty;
Assert.IsTrue(empty2.IsEmpty);
Assert.AreEqual(empty2.Length, 0);
Assert.AreEqual(empty2.Width, 0);
Assert.AreEqual(empty2.Height, 0);
}
#if !WINDOWS_UWP
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_RefConstructor()
{
ReadOnlySpan<int> span = stackalloc[]
{
1, 2, 3, 4, 5, 6
};
ReadOnlySpan2D<int> span2d = ReadOnlySpan2D<int>.DangerousCreate(span[0], 2, 3, 0);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 6);
Assert.AreEqual(span2d.Width, 3);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 1);
Assert.AreEqual(span2d[1, 2], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), -1, 0, 0));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), 1, -2, 0));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ReadOnlySpan2D<int>.DangerousCreate(Unsafe.AsRef<int>(null), 1, 0, -5));
}
#endif
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_PtrConstructor()
{
int* ptr = stackalloc[]
{
1,
2,
3,
4,
5,
6
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(ptr, 2, 3, 0);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 6);
Assert.AreEqual(span2d.Width, 3);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 1);
Assert.AreEqual(span2d[1, 2], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, -1, 0, 0));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, 1, -2, 0));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>((void*)0, 1, 0, -5));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<string>((void*)0, 2, 2, 0));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Array1DConstructor()
{
int[] array =
{
1, 2, 3, 4, 5, 6
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1, 2, 2, 1);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 4);
Assert.AreEqual(span2d.Width, 2);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 2);
Assert.AreEqual(span2d[1, 1], 6);
// Same for ReadOnlyMemory2D<T>, we need to check that covariant array conversions are allowed
_ = new ReadOnlySpan2D<object>(new string[1], 1, 1);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -99, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, -10, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 0, 1, -100, 1));
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array, 0, 10, 1, 120));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Array2DConstructor_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 6);
Assert.AreEqual(span2d.Width, 3);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 1], 2);
Assert.AreEqual(span2d[1, 2], 6);
_ = new ReadOnlySpan2D<object>(new string[1, 2]);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Array2DConstructor_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 4);
Assert.AreEqual(span2d.Width, 2);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 2);
Assert.AreEqual(span2d[1, 1], 6);
_ = new ReadOnlySpan2D<object>(new string[1, 2]);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<object>(new string[1, 2], 0, 0, 2, 2));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Array3DConstructor_1()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 6);
Assert.AreEqual(span2d.Width, 3);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 10);
Assert.AreEqual(span2d[0, 1], 20);
Assert.AreEqual(span2d[1, 2], 60);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 20));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Array3DConstructor_2()
{
int[,,] array =
{
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 10, 20, 30 },
{ 40, 50, 60 }
}
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 1, 0, 1, 2, 2);
Assert.IsFalse(span2d.IsEmpty);
Assert.AreEqual(span2d.Length, 4);
Assert.AreEqual(span2d.Width, 2);
Assert.AreEqual(span2d.Height, 2);
Assert.AreEqual(span2d[0, 0], 20);
Assert.AreEqual(span2d[0, 1], 30);
Assert.AreEqual(span2d[1, 1], 60);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, -1, 1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, -1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 1, 1, 1, 1, -1));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_CopyTo_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
int[] target = new int[array.Length];
span2d.CopyTo(target);
CollectionAssert.AreEqual(array, target);
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span<int>.Empty));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_CopyTo_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
int[] target = new int[4];
span2d.CopyTo(target);
int[] expected = { 2, 3, 5, 6 };
CollectionAssert.AreEqual(target, expected);
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span<int>.Empty));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_CopyTo2D_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
int[,] target = new int[2, 3];
span2d.CopyTo(target);
CollectionAssert.AreEqual(array, target);
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(Span2D<int>.Empty));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_CopyTo2D_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 1, 2, 2);
int[,] target = new int[2, 2];
span2d.CopyTo(target);
int[,] expected =
{
{ 2, 3 },
{ 5, 6 }
};
CollectionAssert.AreEqual(target, expected);
Assert.ThrowsException<ArgumentException>(() => new ReadOnlySpan2D<int>(array).CopyTo(new Span2D<int>(target)));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_TryCopyTo()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
int[] target = new int[array.Length];
Assert.IsTrue(span2d.TryCopyTo(target));
Assert.IsFalse(span2d.TryCopyTo(Span<int>.Empty));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_TryCopyTo2D()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
int[,] target = new int[2, 3];
Assert.IsTrue(span2d.TryCopyTo(target));
Assert.IsFalse(span2d.TryCopyTo(Span2D<int>.Empty));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_GetPinnableReference()
{
Assert.IsTrue(Unsafe.AreSame(
ref Unsafe.AsRef<int>(null),
ref ReadOnlySpan2D<int>.Empty.GetPinnableReference()));
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
ref int r0 = ref span2d.GetPinnableReference();
Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0]));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_DangerousGetReference()
{
Assert.IsTrue(Unsafe.AreSame(
ref Unsafe.AsRef<int>(null),
ref ReadOnlySpan2D<int>.Empty.DangerousGetReference()));
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
ref int r0 = ref span2d.DangerousGetReference();
Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0]));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Slice_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
ReadOnlySpan2D<int> slice1 = span2d.Slice(1, 1, 2, 1);
Assert.AreEqual(slice1.Length, 2);
Assert.AreEqual(slice1.Height, 1);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1[0, 0], 5);
Assert.AreEqual(slice1[0, 1], 6);
ReadOnlySpan2D<int> slice2 = span2d.Slice(0, 1, 2, 2);
Assert.AreEqual(slice2.Length, 4);
Assert.AreEqual(slice2.Height, 2);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2[0, 0], 2);
Assert.AreEqual(slice2[1, 0], 5);
Assert.AreEqual(slice2[1, 1], 6);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(-1, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, -1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, -1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, 1, -1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(10, 1, 1, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 12, 12, 1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).Slice(1, 1, 1, 55));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_Slice_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
ReadOnlySpan2D<int> slice1 = span2d.Slice(0, 0, 2, 2);
Assert.AreEqual(slice1.Length, 4);
Assert.AreEqual(slice1.Height, 2);
Assert.AreEqual(slice1.Width, 2);
Assert.AreEqual(slice1[0, 0], 1);
Assert.AreEqual(slice1[1, 1], 5);
ReadOnlySpan2D<int> slice2 = slice1.Slice(1, 0, 2, 1);
Assert.AreEqual(slice2.Length, 2);
Assert.AreEqual(slice2.Height, 1);
Assert.AreEqual(slice2.Width, 2);
Assert.AreEqual(slice2[0, 0], 4);
Assert.AreEqual(slice2[0, 1], 5);
ReadOnlySpan2D<int> slice3 = slice2.Slice(0, 1, 1, 1);
Assert.AreEqual(slice3.Length, 1);
Assert.AreEqual(slice3.Height, 1);
Assert.AreEqual(slice3.Width, 1);
Assert.AreEqual(slice3[0, 0], 5);
}
#if !WINDOWS_UWP
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_GetRowReadOnlySpan()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
ReadOnlySpan<int> span = span2d.GetRowSpan(1);
Assert.IsTrue(Unsafe.AreSame(
ref Unsafe.AsRef(span[0]),
ref array[1, 0]));
Assert.IsTrue(Unsafe.AreSame(
ref Unsafe.AsRef(span[2]),
ref array[1, 2]));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRowSpan(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRowSpan(5));
}
#endif
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
bool success = span2d.TryGetSpan(out ReadOnlySpan<int> span);
#if WINDOWS_UWP
// Can't get a ReadOnlySpan<T> over a T[,] array on UWP
Assert.IsFalse(success);
Assert.AreEqual(span.Length, 0);
#else
Assert.IsTrue(success);
Assert.AreEqual(span.Length, span2d.Length);
#endif
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
bool success = span2d.TryGetSpan(out ReadOnlySpan<int> span);
Assert.IsFalse(success);
Assert.IsTrue(span.IsEmpty);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ToArray_1()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
int[,] copy = span2d.ToArray();
Assert.AreEqual(copy.GetLength(0), array.GetLength(0));
Assert.AreEqual(copy.GetLength(1), array.GetLength(1));
CollectionAssert.AreEqual(array, copy);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ToArray_2()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
int[,] copy = span2d.ToArray();
Assert.AreEqual(copy.GetLength(0), 2);
Assert.AreEqual(copy.GetLength(1), 2);
int[,] expected =
{
{ 1, 2 },
{ 4, 5 }
};
CollectionAssert.AreEqual(expected, copy);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Test_ReadOnlySpan2DT_Equals()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
_ = span2d.Equals(null);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void Test_ReadOnlySpan2DT_GetHashCode()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
_ = span2d.GetHashCode();
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ToString()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d = new ReadOnlySpan2D<int>(array);
string text = span2d.ToString();
const string expected = "Microsoft.Toolkit.HighPerformance.Memory.ReadOnlySpan2D<System.Int32>[2, 3]";
Assert.AreEqual(text, expected);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_opEquals()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d_1 = new ReadOnlySpan2D<int>(array);
ReadOnlySpan2D<int> span2d_2 = new ReadOnlySpan2D<int>(array);
Assert.IsTrue(span2d_1 == span2d_2);
Assert.IsFalse(span2d_1 == ReadOnlySpan2D<int>.Empty);
Assert.IsTrue(ReadOnlySpan2D<int>.Empty == ReadOnlySpan2D<int>.Empty);
ReadOnlySpan2D<int> span2d_3 = new ReadOnlySpan2D<int>(array, 0, 0, 2, 2);
Assert.IsFalse(span2d_1 == span2d_3);
Assert.IsFalse(span2d_3 == ReadOnlySpan2D<int>.Empty);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ImplicitCast()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
ReadOnlySpan2D<int> span2d_1 = array;
ReadOnlySpan2D<int> span2d_2 = new ReadOnlySpan2D<int>(array);
Assert.IsTrue(span2d_1 == span2d_2);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_GetRow()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
int i = 0;
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array).GetRow(1))
{
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[1, i++]));
}
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array).GetRow(1);
int[] expected = { 4, 5, 6 };
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
Assert.AreSame(default(ReadOnlyRefEnumerable<int>).ToArray(), Array.Empty<int>());
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(2));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetRow(1000));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetRow()
{
int* array = stackalloc[]
{
1, 2, 3,
4, 5, 6
};
int i = 0;
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1))
{
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[3 + i++]));
}
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1);
int[] expected = { 4, 5, 6 };
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(2));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetRow(1000));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_GetColumn()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
int i = 0;
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array).GetColumn(1))
{
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[i++, 1]));
}
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array).GetColumn(2);
int[] expected = { 3, 6 };
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array).GetColumn(1000));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetColumn()
{
int* array = stackalloc[]
{
1, 2, 3,
4, 5, 6
};
int i = 0;
foreach (ref readonly int value in new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(1))
{
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(value), ref array[(i++ * 3) + 1]));
}
ReadOnlyRefEnumerable<int> enumerable = new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(2);
int[] expected = { 3, 6 };
CollectionAssert.AreEqual(enumerable.ToArray(), expected);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(3));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new ReadOnlySpan2D<int>(array, 2, 3, 0).GetColumn(1000));
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_GetEnumerator()
{
int[,] array =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};
int[] result = new int[4];
int i = 0;
foreach (var item in new ReadOnlySpan2D<int>(array, 0, 1, 2, 2))
{
result[i++] = item;
}
int[] expected = { 2, 3, 5, 6 };
CollectionAssert.AreEqual(result, expected);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public unsafe void Test_ReadOnlySpan2DT_Pointer_GetEnumerator()
{
int* array = stackalloc[]
{
1, 2, 3,
4, 5, 6
};
int[] result = new int[4];
int i = 0;
foreach (var item in new ReadOnlySpan2D<int>(array + 1, 2, 2, 1))
{
result[i++] = item;
}
int[] expected = { 2, 3, 5, 6 };
CollectionAssert.AreEqual(result, expected);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_GetEnumerator_Empty()
{
var enumerator = ReadOnlySpan2D<int>.Empty.GetEnumerator();
Assert.IsFalse(enumerator.MoveNext());
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Misc()
{
int[,] array1 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
ReadOnlySpan2D<int> span1 = array1;
int[,] array2 = new int[4, 4];
// Copy to enumerable with source step == 1, destination step == 1
span1.GetRow(0).CopyTo(array2.GetRow(0));
// Copy enumerable with source step == 1, destination step != 1
span1.GetRow(1).CopyTo(array2.GetColumn(1));
// Copy enumerable with source step != 1, destination step == 1
span1.GetColumn(2).CopyTo(array2.GetRow(2));
// Copy enumerable with source step != 1, destination step != 1
span1.GetColumn(3).CopyTo(array2.GetColumn(3));
int[,] result =
{
{ 1, 5, 3, 4 },
{ 0, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 0, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
// Test a valid and an invalid TryCopyTo call with the RefEnumerable<T> overload
bool shouldBeTrue = span1.GetRow(0).TryCopyTo(array2.GetColumn(0));
bool shouldBeFalse = span1.GetRow(0).TryCopyTo(default(RefEnumerable<int>));
result = new[,]
{
{ 1, 5, 3, 4 },
{ 2, 6, 0, 8 },
{ 3, 7, 11, 12 },
{ 4, 8, 0, 16 }
};
CollectionAssert.AreEqual(array2, result);
Assert.IsTrue(shouldBeTrue);
Assert.IsFalse(shouldBeFalse);
}
[TestCategory("ReadOnlySpan2DT")]
[TestMethod]
public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Cast()
{
int[,] array1 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 }
};
int[] result = { 5, 6, 7, 8 };
// Cast a RefEnumerable<T> to a readonly one and verify the contents
int[] row = ((ReadOnlyRefEnumerable<int>)array1.GetRow(1)).ToArray();
CollectionAssert.AreEqual(result, row);
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -78,6 +78,42 @@ namespace UnitTests.HighPerformance.Streams
Assert.AreEqual(stream.Position, 32);
}
// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3536
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_WriteToEndAndRefreshPosition()
{
byte[]
array = new byte[10],
temp = new byte[1];
ReadOnlyMemory<byte> memory = array;
using var stream = memory.AsStream();
for (int i = 0; i < array.Length; i++)
{
int read = stream.Read(temp, 0, 1);
Assert.AreEqual(read, 1);
Assert.AreEqual(stream.Position, i + 1);
}
Assert.AreEqual(stream.Position, array.Length);
// These should not throw, seeking to the end is valid
stream.Position = stream.Position;
Assert.AreEqual(stream.Position, array.Length);
stream.Seek(array.Length, SeekOrigin.Begin);
Assert.AreEqual(stream.Position, array.Length);
stream.Seek(0, SeekOrigin.Current);
Assert.AreEqual(stream.Position, array.Length);
stream.Seek(0, SeekOrigin.End);
Assert.AreEqual(stream.Position, array.Length);
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ReadWrite_Array()

Просмотреть файл

@ -16,8 +16,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_StringPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\TrackingArrayPool{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.3D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.1D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayPoolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IMemoryOwnerExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlyMemoryExtensions.cs" />
@ -33,12 +34,18 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpinLockExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_StringExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_HashCode{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.Ref2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.In2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ThrowExceptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.For.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.For2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.In.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_ParallelHelper.ForEach.Ref.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Test_BitHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_ReadOnlyMemory2D{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_ReadOnlySpan2D{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_Memory2D{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Memory\Test_Span2D{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_IMemoryOwnerStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.ThrowExceptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.cs" />

Просмотреть файл

@ -13,161 +13,6 @@ namespace UnitTests.Extensions
[TestClass]
public class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayMid()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 1, 3, 2);
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, true, true, true, false },
{ false, true, true, true, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayTwice()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 0, 0, 1, 2);
test.Fill(true, 1, 3, 2, 2);
var expected = new bool[,]
{
{ true, false, false, false, false },
{ true, false, false, true, true },
{ false, false, false, true, true },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayNegativeSize()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 3, 4, -3, -2);
// TODO: We may want to think about this pattern in the future:
/*var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, true, true, true },
{ false, false, true, true, true },
};*/
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayBottomEdgeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 2, 2, 4);
var expected = new bool[,]
{
{ false, false, false, false, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayTopLeftCornerNegativeBoundary()
{
bool[,] test = new bool[4, 5];
test.Fill(true, -1, -1, 3, 3);
var expected = new bool[,]
{
{ true, true, false, false, false },
{ true, true, false, false, false },
{ false, false, false, false, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_FillArrayBottomRightCornerBoundary()
{
bool[,] test = new bool[5, 4];
test.Fill(true, 3, 2, 3, 3);
var expected = new bool[,]
{
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false },
{ false, false, true, true },
{ false, false, true, true },
};
CollectionAssert.AreEqual(
expected,
test,
"Fill failed. Expected:\n{0}.\nActual:\n{1}",
expected.ToArrayString(),
test.ToArrayString());
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Jagged_GetColumn()
@ -206,82 +51,6 @@ namespace UnitTests.Extensions
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetColumn()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 9 },
{ 7, -1, 0 }
};
var col = array.GetColumn(1).ToArray();
CollectionAssert.AreEquivalent(new int[] { 2, 3, -1 }, col);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetColumn_Exception()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 0 },
{ 7, 0, 0 }
};
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(-1).ToArray();
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetColumn(3).ToArray();
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetRow()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 9 },
{ 7, -1, 0 }
};
var col = array.GetRow(1).ToArray();
CollectionAssert.AreEquivalent(new int[] { 6, 3, 9 }, col);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_GetRow_Exception()
{
int[,] array =
{
{ 5, 2, 4 },
{ 6, 3, 0 },
{ 7, 0, 0 }
};
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetRow(-1).ToArray();
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
array.GetRow(3).ToArray();
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Rectangular_ToString()

Просмотреть файл

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI.Controls;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
using Windows.UI.Xaml.Automation.Peers;
namespace UnitTests.UWP.UI.Controls
{
[TestClass]
public class Test_RadialGauge
{
/// <summary>
/// Verifies that the UIA name is valid and makes sense
/// </summary>
[TestCategory("Test_TextToolbar_Localization")]
[UITestMethod]
public void VerifyUIAName()
{
var gauge = new RadialGauge()
{
Minimum = 0,
Maximum = 100,
Value = 20
};
var gaugePeer = FrameworkElementAutomationPeer.CreatePeerForElement(gauge);
Assert.IsTrue(gaugePeer.GetName().Contains(gauge.Value.ToString()), "Verify that the UIA name contains the value of the RadialGauge.");
Assert.IsTrue(gaugePeer.GetName().Contains("no unit"), "The UIA name should indicate that unit was not specified.");
gauge.Unit = "KM/H";
Assert.IsTrue(gaugePeer.GetName().Contains(gauge.Unit), "The UIA name should report the unit of the RadialGauge.");
}
}
}

Просмотреть файл

@ -13,11 +13,31 @@ namespace UnitTests.UI.Controls
[TestClass]
public class Test_UniformGrid_FreeSpots
{
/// <summary>
/// Creates a <see cref="TakenSpotsReferenceHolder"/> instance with the specified values.
/// </summary>
/// <param name="array">The source array to populate the instance to return.</param>
/// <returns>A <see cref="TakenSpotsReferenceHolder"/> with the given values.</returns>
private static TakenSpotsReferenceHolder CreateTakenSpotsReferenceHolder(bool[,] array)
{
var refHolder = new TakenSpotsReferenceHolder(array.GetLength(0), array.GetLength(1));
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
refHolder[i, j] = array[i, j];
}
}
return refHolder;
}
[TestCategory("UniformGrid")]
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_Basic()
{
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
{
{ false, true, false, true, false },
{ false, true, true, true, false },
@ -47,7 +67,7 @@ namespace UnitTests.UI.Controls
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_FirstColumn()
{
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
{
{ true, false, false, true, false },
{ false, true, true, true, false },
@ -77,7 +97,7 @@ namespace UnitTests.UI.Controls
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBoundMinusOne()
{
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
{
{ false, false, false },
{ false, false, false },
@ -105,7 +125,7 @@ namespace UnitTests.UI.Controls
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBound()
{
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
{
{ false, false, false },
{ false, false, false },
@ -133,7 +153,7 @@ namespace UnitTests.UI.Controls
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_FirstColumnEndBound_TopDown()
{
var testRef = new TakenSpotsReferenceHolder(new bool[3, 3]
var testRef = CreateTakenSpotsReferenceHolder(new bool[3, 3]
{
{ false, false, false },
{ false, false, false },
@ -161,7 +181,7 @@ namespace UnitTests.UI.Controls
[UITestMethod]
public void Test_UniformGrid_GetFreeSpots_VerticalOrientation()
{
var testRef = new TakenSpotsReferenceHolder(new bool[4, 5]
var testRef = CreateTakenSpotsReferenceHolder(new bool[4, 5]
{
{ false, false, false, true, false },
{ false, true, true, false, false },

Просмотреть файл

@ -179,6 +179,7 @@
<Compile Include="PrivateType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\Test_WeakEventListener.cs" />
<Compile Include="UI\Controls\Test_RadialGauge.cs" />
<Compile Include="UI\Controls\Test_TextToolbar_Localization.cs" />
<Compile Include="UI\Controls\Test_InfiniteCanvas_Regression.cs" />
<Compile Include="UI\Controls\Test_TokenizingTextBox_General.cs" />
@ -498,4 +499,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>