Merge branch 'master' into robloo/reimagined-color-picker

This commit is contained in:
robloo 2020-11-08 22:31:46 -05:00
Родитель 4064179663 43693f8c58
Коммит 5a05f83d79
92 изменённых файлов: 12395 добавлений и 1628 удалений

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

@ -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();
}

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

@ -12,26 +12,11 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
public class NetworkHelper
{
/// <summary>
/// Private singleton field.
/// </summary>
private static NetworkHelper _instance;
/// <summary>
/// Event raised when the network changes.
/// </summary>
public event EventHandler NetworkChanged;
/// <summary>
/// Gets public singleton property.
/// </summary>
public static NetworkHelper Instance => _instance ?? (_instance = new NetworkHelper());
/// <summary>
/// Gets instance of <see cref="ConnectionInformation"/>.
/// </summary>
public ConnectionInformation ConnectionInformation { get; } = new ConnectionInformation();
/// <summary>
/// Initializes a new instance of the <see cref="NetworkHelper"/> class.
/// </summary>
@ -52,6 +37,19 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
NetworkInformation.NetworkStatusChanged -= OnNetworkStatusChanged;
}
/// <summary>
/// Gets public singleton property.
/// </summary>
public static NetworkHelper Instance { get; } = new NetworkHelper();
/// <summary>
/// Gets instance of <see cref="ConnectionInformation"/>.
/// </summary>
public ConnectionInformation ConnectionInformation { get; }
/// <summary>
/// Checks the current connection information and raises <see cref="NetworkChanged"/> if needed.
/// </summary>
private void UpdateConnectionInformation()
{
lock (ConnectionInformation)
@ -69,6 +67,9 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
}
}
/// <summary>
/// Invokes <see cref="UpdateConnectionInformation"/> when the current network status changes.
/// </summary>
private void OnNetworkStatusChanged(object sender)
{
UpdateConnectionInformation();

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

@ -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;
}
}
}

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

@ -20,6 +20,18 @@ namespace Microsoft.Toolkit.Diagnostics
/// </summary>
public static partial class ThrowHelper
{
/// <summary>
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="ArrayTypeMismatchException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowArrayTypeMismatchException<T>()
{
throw new ArrayTypeMismatchException();
}
/// <summary>
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
/// </summary>
@ -47,6 +59,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArrayTypeMismatchException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="ArgumentException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="ArgumentException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowArgumentException<T>()
{
throw new ArgumentException();
}
/// <summary>
/// Throws a new <see cref="ArgumentException"/>.
/// </summary>
@ -103,6 +127,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArgumentException(message, name, innerException);
}
/// <summary>
/// Throws a new <see cref="ArgumentNullException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="ArgumentNullException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowArgumentNullException<T>()
{
throw new ArgumentNullException();
}
/// <summary>
/// Throws a new <see cref="ArgumentNullException"/>.
/// </summary>
@ -144,6 +180,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArgumentNullException(name, message);
}
/// <summary>
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="ArgumentOutOfRangeException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowArgumentOutOfRangeException<T>()
{
throw new ArgumentOutOfRangeException();
}
/// <summary>
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
/// </summary>
@ -201,6 +249,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#if !NETSTANDARD1_4
/// <summary>
/// Throws a new <see cref="COMException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="COMException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowCOMException<T>()
{
throw new COMException();
}
/// <summary>
/// Throws a new <see cref="COMException"/>.
/// </summary>
@ -242,6 +302,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new COMException(message, error);
}
/// <summary>
/// Throws a new <see cref="ExternalException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="ExternalException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowExternalException<T>()
{
throw new ExternalException();
}
/// <summary>
/// Throws a new <see cref="ExternalException"/>.
/// </summary>
@ -284,6 +356,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="FormatException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="FormatException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowFormatException<T>()
{
throw new FormatException();
}
/// <summary>
/// Throws a new <see cref="FormatException"/>.
/// </summary>
@ -312,6 +396,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#if !NETSTANDARD1_4
/// <summary>
/// Throws a new <see cref="InsufficientMemoryException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="InsufficientMemoryException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowInsufficientMemoryException<T>()
{
throw new InsufficientMemoryException();
}
/// <summary>
/// Throws a new <see cref="InsufficientMemoryException"/>.
/// </summary>
@ -340,6 +436,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="InvalidDataException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="InvalidDataException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowInvalidDataException<T>()
{
throw new InvalidDataException();
}
/// <summary>
/// Throws a new <see cref="InvalidDataException"/>.
/// </summary>
@ -367,6 +475,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new InvalidDataException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="InvalidOperationException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="InvalidOperationException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowInvalidOperationException<T>()
{
throw new InvalidOperationException();
}
/// <summary>
/// Throws a new <see cref="InvalidOperationException"/>.
/// </summary>
@ -394,6 +514,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new InvalidOperationException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="LockRecursionException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="LockRecursionException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowLockRecursionException<T>()
{
throw new LockRecursionException();
}
/// <summary>
/// Throws a new <see cref="LockRecursionException"/>.
/// </summary>
@ -421,6 +553,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new LockRecursionException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="MissingFieldException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="MissingFieldException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowMissingFieldException<T>()
{
throw new MissingFieldException();
}
/// <summary>
/// Throws a new <see cref="MissingFieldException"/>.
/// </summary>
@ -464,6 +608,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="MissingMemberException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="MissingMemberException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowMissingMemberException<T>()
{
throw new MissingMemberException();
}
/// <summary>
/// Throws a new <see cref="MissingMemberException"/>.
/// </summary>
@ -507,6 +663,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="MissingMethodException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="MissingMethodException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowMissingMethodException<T>()
{
throw new MissingMethodException();
}
/// <summary>
/// Throws a new <see cref="MissingMethodException"/>.
/// </summary>
@ -550,6 +718,18 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="NotSupportedException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="NotSupportedException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowNotSupportedException<T>()
{
throw new NotSupportedException();
}
/// <summary>
/// Throws a new <see cref="NotSupportedException"/>.
/// </summary>
@ -618,6 +798,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ObjectDisposedException(objectName, message);
}
/// <summary>
/// Throws a new <see cref="OperationCanceledException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="OperationCanceledException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowOperationCanceledException<T>()
{
throw new OperationCanceledException();
}
/// <summary>
/// Throws a new <see cref="OperationCanceledException"/>.
/// </summary>
@ -687,6 +879,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new OperationCanceledException(message, innerException, token);
}
/// <summary>
/// Throws a new <see cref="PlatformNotSupportedException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="PlatformNotSupportedException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowPlatformNotSupportedException<T>()
{
throw new PlatformNotSupportedException();
}
/// <summary>
/// Throws a new <see cref="PlatformNotSupportedException"/>.
/// </summary>
@ -714,6 +918,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new PlatformNotSupportedException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="SynchronizationLockException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="SynchronizationLockException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowSynchronizationLockException<T>()
{
throw new SynchronizationLockException();
}
/// <summary>
/// Throws a new <see cref="SynchronizationLockException"/>.
/// </summary>
@ -741,6 +957,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new SynchronizationLockException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="TimeoutException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="TimeoutException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowTimeoutException<T>()
{
throw new TimeoutException();
}
/// <summary>
/// Throws a new <see cref="TimeoutException"/>.
/// </summary>
@ -768,6 +996,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new TimeoutException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="UnauthorizedAccessException"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="UnauthorizedAccessException">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowUnauthorizedAccessException<T>()
{
throw new UnauthorizedAccessException();
}
/// <summary>
/// Throws a new <see cref="UnauthorizedAccessException"/>.
/// </summary>
@ -795,6 +1035,18 @@ namespace Microsoft.Toolkit.Diagnostics
throw new UnauthorizedAccessException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="Win32Exception"/>.
/// </summary>
/// <typeparam name="T">The type of expected result.</typeparam>
/// <exception cref="Win32Exception">Thrown with no parameters.</exception>
/// <returns>This method always throws, so it actually never returns a value.</returns>
[DoesNotReturn]
public static T ThrowWin32Exception<T>()
{
throw new Win32Exception();
}
/// <summary>
/// Throws a new <see cref="Win32Exception"/>.
/// </summary>

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

@ -20,6 +20,16 @@ namespace Microsoft.Toolkit.Diagnostics
/// </summary>
public static partial class ThrowHelper
{
/// <summary>
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
/// </summary>
/// <exception cref="ArrayTypeMismatchException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowArrayTypeMismatchException()
{
throw new ArrayTypeMismatchException();
}
/// <summary>
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
/// </summary>
@ -43,6 +53,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArrayTypeMismatchException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="ArgumentException"/>.
/// </summary>
/// <exception cref="ArgumentException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowArgumentException()
{
throw new ArgumentException();
}
/// <summary>
/// Throws a new <see cref="ArgumentException"/>.
/// </summary>
@ -91,6 +111,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArgumentException(message, name, innerException);
}
/// <summary>
/// Throws a new <see cref="ArgumentNullException"/>.
/// </summary>
/// <exception cref="ArgumentNullException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowArgumentNullException()
{
throw new ArgumentNullException();
}
/// <summary>
/// Throws a new <see cref="ArgumentNullException"/>.
/// </summary>
@ -126,6 +156,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ArgumentNullException(name, message);
}
/// <summary>
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException();
}
/// <summary>
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
/// </summary>
@ -175,6 +215,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#if !NETSTANDARD1_4
/// <summary>
/// Throws a new <see cref="COMException"/>.
/// </summary>
/// <exception cref="COMException">Thrown with no paarameters.</exception>
[DoesNotReturn]
public static void ThrowCOMException()
{
throw new COMException();
}
/// <summary>
/// Throws a new <see cref="COMException"/>.
/// </summary>
@ -210,6 +260,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new COMException(message, error);
}
/// <summary>
/// Throws a new <see cref="ExternalException"/>.
/// </summary>
/// <exception cref="ExternalException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowExternalException()
{
throw new ExternalException();
}
/// <summary>
/// Throws a new <see cref="ExternalException"/>.
/// </summary>
@ -246,6 +306,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="FormatException"/>.
/// </summary>
/// <exception cref="FormatException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowFormatException()
{
throw new FormatException();
}
/// <summary>
/// Throws a new <see cref="FormatException"/>.
/// </summary>
@ -270,6 +340,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#if !NETSTANDARD1_4
/// <summary>
/// Throws a new <see cref="InsufficientMemoryException"/>.
/// </summary>
/// <exception cref="InsufficientMemoryException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowInsufficientMemoryException()
{
throw new InsufficientMemoryException();
}
/// <summary>
/// Throws a new <see cref="InsufficientMemoryException"/>.
/// </summary>
@ -294,6 +374,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="InvalidDataException"/>.
/// </summary>
/// <exception cref="InvalidDataException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowInvalidDataException()
{
throw new InvalidDataException();
}
/// <summary>
/// Throws a new <see cref="InvalidDataException"/>.
/// </summary>
@ -317,6 +407,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new InvalidDataException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="InvalidOperationException"/>.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowInvalidOperationException()
{
throw new InvalidOperationException();
}
/// <summary>
/// Throws a new <see cref="InvalidOperationException"/>.
/// </summary>
@ -340,6 +440,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new InvalidOperationException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="LockRecursionException"/>.
/// </summary>
/// <exception cref="LockRecursionException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowLockRecursionException()
{
throw new LockRecursionException();
}
/// <summary>
/// Throws a new <see cref="LockRecursionException"/>.
/// </summary>
@ -363,6 +473,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new LockRecursionException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="MissingFieldException"/>.
/// </summary>
/// <exception cref="MissingFieldException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowMissingFieldException()
{
throw new MissingFieldException();
}
/// <summary>
/// Throws a new <see cref="MissingFieldException"/>.
/// </summary>
@ -400,6 +520,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="MissingMemberException"/>.
/// </summary>
/// <exception cref="MissingMemberException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowMissingMemberException()
{
throw new MissingMemberException();
}
/// <summary>
/// Throws a new <see cref="MissingMemberException"/>.
/// </summary>
@ -437,6 +567,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="MissingMethodException"/>.
/// </summary>
/// <exception cref="MissingMethodException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowMissingMethodException()
{
throw new MissingMethodException();
}
/// <summary>
/// Throws a new <see cref="MissingMethodException"/>.
/// </summary>
@ -474,6 +614,16 @@ namespace Microsoft.Toolkit.Diagnostics
}
#endif
/// <summary>
/// Throws a new <see cref="NotSupportedException"/>.
/// </summary>
/// <exception cref="NotSupportedException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowNotSupportedException()
{
throw new NotSupportedException();
}
/// <summary>
/// Throws a new <see cref="NotSupportedException"/>.
/// </summary>
@ -532,6 +682,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new ObjectDisposedException(objectName, message);
}
/// <summary>
/// Throws a new <see cref="OperationCanceledException"/>.
/// </summary>
/// <exception cref="OperationCanceledException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowOperationCanceledException()
{
throw new OperationCanceledException();
}
/// <summary>
/// Throws a new <see cref="OperationCanceledException"/>.
/// </summary>
@ -591,6 +751,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new OperationCanceledException(message, innerException, token);
}
/// <summary>
/// Throws a new <see cref="PlatformNotSupportedException"/>.
/// </summary>
/// <exception cref="PlatformNotSupportedException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowPlatformNotSupportedException()
{
throw new PlatformNotSupportedException();
}
/// <summary>
/// Throws a new <see cref="PlatformNotSupportedException"/>.
/// </summary>
@ -614,6 +784,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new PlatformNotSupportedException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="SynchronizationLockException"/>.
/// </summary>
/// <exception cref="SynchronizationLockException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowSynchronizationLockException()
{
throw new SynchronizationLockException();
}
/// <summary>
/// Throws a new <see cref="SynchronizationLockException"/>.
/// </summary>
@ -637,6 +817,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new SynchronizationLockException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="TimeoutException"/>.
/// </summary>
/// <exception cref="TimeoutException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowTimeoutException()
{
throw new TimeoutException();
}
/// <summary>
/// Throws a new <see cref="TimeoutException"/>.
/// </summary>
@ -660,6 +850,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new TimeoutException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="UnauthorizedAccessException"/>.
/// </summary>
/// <exception cref="UnauthorizedAccessException">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowUnauthorizedAccessException()
{
throw new UnauthorizedAccessException();
}
/// <summary>
/// Throws a new <see cref="UnauthorizedAccessException"/>.
/// </summary>
@ -683,6 +883,16 @@ namespace Microsoft.Toolkit.Diagnostics
throw new UnauthorizedAccessException(message, innerException);
}
/// <summary>
/// Throws a new <see cref="Win32Exception"/>.
/// </summary>
/// <exception cref="Win32Exception">Thrown with no parameters.</exception>
[DoesNotReturn]
public static void ThrowWin32Exception()
{
throw new Win32Exception();
}
/// <summary>
/// Throws a new <see cref="Win32Exception"/>.
/// </summary>

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

@ -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>

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

@ -1,117 +1,39 @@
# Contributing to the Windows Community Toolkit
# Contributing to the Windows Community Toolkit :sparkles::sparkles:
The foundation of the **Windows Community Toolkit** is simplicity.
Thank you for exhibiting interest in contributing to the Windows Community Toolkit. The team is delighted to welcome you onboard to our exciting and growing project. Any contribution or value added go a long way to enhance the project!
A developer should be able to quickly and easily learn to use the API.
In the next few steps, you will be able to see a glimpse of ways you can contribute to the Windows Community Toolkit.
Simplicity and a low barrier to entry are must-have features of every API. If you have any second thoughts about the complexity of a design, it is almost always much better to cut the feature from the current release and spend more time to get the design right for the next release.
:rotating_light: **It is highly recommended to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) where you can find complete and detail-oriented content of this page** :rotating_light:
You can always add to an API, you cannot ever remove anything from one. If the design does not feel right, and you ship it anyway, you are likely to regret having done so.
## <a name="question"></a> Questions :grey_question:
Due to the high volume of incoming issues please keep our GitHub issues for bug reports and feature requests. For general questions, there is a higher chance of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag windows-community-toolkit.
That's why many of the guidelines of this document are obvious and serve only one purpose: Simplicity.
For missing documentation related question, please file an issue at [Microsoft Docs](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/issues/new).
- [Questions](#question)
- [Issues or Bugs](#issue)
- [Submitting a pull request](#pr)
- [Quality assurance for pull requests for XAML controls](#xaml)
- [General rules](#rules)
- [Naming conventions](#naming)
- [Documentation](#documentation)
- [Files and folders](#files)
## <a name="bug"></a> Fix a Bug :bug:
If you find any bug, you can help the community by [submitting an issue](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=bug+%3Abug%3A&template=bug_report.md&title=). Once the issue is filed, feel free to start working on the PR and submit a PR.
## <a name="issue"></a>Good First Issue :ok_hand:
If this is your first time contributing to the Windows Community Toolkit and do not have advanced level programming experience, we have got you covered :boom: WCT has a list of [good first issue](https://github.com/windows-toolkit/WindowsCommunityToolkit/labels/good%20first%20issue%20%3Aok_hand%3A) that can be a great entryway to find and fix any issues that best fit your expertise or technical background.
## <a name="question"></a> Questions
Please do not open issues for general support questions and keep our GitHub issues for bug reports and feature requests. There is a much better chance of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag `windows-community-toolkit`
## <a name="help"></a>Help Wanted :raising_hand:
WCT has a list of issues that are labeled as [help wanted](https://github.com/windows-toolkit/WindowsCommunityToolkit/labels/help%20wanted%20%3Araising_hand%3A). The level of complexity in the list can vary but if you have an advanced level of programming experience, feel free to jump in to solve these issues.
## <a name="issue"></a> Found a Bug?
If you find a bug, you can help us by
[submitting an issue](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues). Even better, you can
[submit a Pull Request](#pr) with a fix.
## <a name="feature"></a>Add New Feature :mailbox_with_mail:
* To contribute a new feature, fill out the [Feature Request Template](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=feature+request+%3Amailbox_with_mail%3A&template=feature_request.md&title=%5BFeature%5D) and provide detailed information to express the proposal.
* Once the Feature Request is submitted, it will be open for discussion.
* If it gets approved by the team, proceed to submit a PR of the proposed Feature.
* If the PR contains an error-free code and the reviewer signs off, the PR will be merged.
## <a name="pr"></a> Submitting a pull request
For every contribution, you must:
## <a name="docs"></a> Add or Improve Documentation :page_with_curl:
* test your code with the [supported SDKs](readme.md#supported)
* follow the [quality guidance](#xaml), [general rules](#rules) and [naming convention](#naming)
* target master branch (or an appropriate release branch if appropriate for a bug fix)
Due to the involvement of multiple steps to add or improve documents; it is required to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) and follow contribution guidelines.
* If adding a new feature
* Before starting coding, **you should open an [issue](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=feature+request+%3Amailbox_with_mail%3A&template=feature_request.md&title=%5BFeature%5D)** and start discussing with the community to see if your idea/feature is interesting enough.
* Add or update a sample for the [Sample app](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.SampleApp)
* If creating a new sample, create a new icon by following the [Thumbnail Style Guide and templates](https://github.com/Microsoft/UWPCommunityToolkit-design-assets)
* Add or update unit tests (if applicable)
## <a name="pr"></a>Create, Submit or Review Pull Request :rocket:
Anyone with write access can create a Pull Request by forking the Windows Community Toolkit Repository. Here is how you can [Create a Pull Request from fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork). Once you fork the Windows Community Toolkit repo, it is essential to create all changes in the feature branch of your forked repository. If you have the changes in the forked feature branch, you can then create a Pull Request in the main Windows Community Toolkit.
PR has to be validated by at least two core members before being merged.
Please visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) for detailed information and steps it requires to Submit or Review Pull Request.
Once merged, you can get a pre-release package of the toolkit by adding this ([Nuget repo](https://dotnet.myget.org/F/uwpcommunitytoolkit/api/v3/index.json) | [Gallery](https://dotnet.myget.org/gallery/uwpcommunitytoolkit)) to your Visual Studio.
### <a name="docs"></a> Adding Documentation
Documentation is **required** when adding, removing, or updating a control or an API. To update the documentation, you must submit a separate Pull Request in the [documentation repository](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs) (use the *master* branch). **Both Pull Requests (code and docs) must be approved by the core team before either one is merged.**
Make sure to update both Pull Requests with a link to each other.
If adding a new documentation page:
* Copy the [documentation template](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/.template.md) and follow the same format.
* Update the [Table of Contents](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/toc.md) to point to the new page
## <a name="xaml"></a> Quality assurance for pull requests for XAML controls
We encourage developers to follow the following guidances when submitting pull requests for controls:
* Your control must be usable and efficient with keyboard only
* Tab order must be logical
* Focused controls must be visible
* Action must be triggered when hitting Enter key
* Do not use custom colors but instead rely on theme colors so high contrasts themes can be used with your control
* Add AutomationProperties.Name on all controls to define what the controls purpose (Name is minimum, but there are some other things too that can really help the screen reader).
* Don't use the same Name on two different elements unless they have different control types
* Use Narrator Dev mode (Launch Narrator [WinKey+Enter], then CTRL+F12) to test the screen reader experience. Is the information sufficient, meaningful and helps the user navigate and understand your control
* Ensure that you have run your xaml file changes through Xaml Styler (version 2.3+), which can be downloaded from [here](https://visualstudiogallery.msdn.microsoft.com/3de2a3c6-def5-42c4-924d-cc13a29ff5b7). Do not worry about the settings for this as they are set at the project level (settings.xamlstyler).
You can find more information about these topics [here](https://blogs.msdn.microsoft.com/winuiautomation/2015/07/14/building-accessible-windows-universal-apps-introduction)
This is to help as part of our effort to build an accessible toolkit (starting with 1.2)
## <a name="rules"></a> General rules
* DO NOT require that users perform any extensive initialization before they can start programming basic scenarios.
* DO provide good defaults for all values associated with parameters, options, etc.
* DO ensure that APIs are intuitive and can be successfully used in basic scenarios without referring to the reference documentation.
* DO communicate incorrect usage of APIs as soon as possible.
* DO design an API by writing code samples for the main scenarios. Only then, you define the object model that supports those code samples.
* DO NOT use regions. DO use partial classes instead.
* DO declare static dependency properties at the top of their file.
* DO NOT seal controls.
* DO use extension methods over static methods where possible.
* DO NOT return true or false to give success status. Throw exceptions if there was a failure.
* DO use verbs like GET.
* DO NOT use verbs that are not already used like fetch.
## <a name="accessibility"></a> Accessibility Guideline
We'll follow this guideline to ensure the basic accessibility features for each control.
### Color & High Contrast themes
* Controls must support the 4 high contrast themes by default on Windows, in addition to changing the theme while the app is running.
* Controls must have a contrast ratio of at least 4.5:1 between the text (and images with text) and the background behind it.
### Keyboard
* Controls must support keyboard navigation (tabs and arrow keys), the tab order must be the same as the UI and non-interactive elements mustn't be focusable.
* Composite elements must ensure proper inner navigation among the contained elements
* Clickable UI elements must be invokable with the keyboard (The trigger keys are enter and space).
* Focusable elements must have a visual focus indicator. It's usually a rectangle shape around the control's normal bounding rectangle.
### Narrator
* Controls must support narrator.
## <a name="naming"></a> Naming conventions
* We are following the coding guidelines of [.NET Core Foundational libraries](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md).
## <a name="documentation"></a> Documentation
* DO NOT expect that your API is so well designed that it needs no documentation. No API is that intuitive.
* DO provide great documentation with all APIs.
* DO use readable and self-documenting identifier names.
* DO use consistent naming and terminology.
* DO provide strongly typed APIs.
* DO use verbose identifier names.
## <a name="files"></a> Files and folders
* DO associate no more than one class per file.
* DO use folders to group classes based on features.
# ThankYou :heart::heart:
**Thank you so much for contributing to this amazing project. We hope you will continue to add value and find yourself as a highly reliable source to the Windows Community Toolkit**

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

@ -1,86 +1,44 @@
---
topic: sample
languages:
- csharp
products:
- windows
---
# Windows Community Toolkit
# Windows Community Toolkit :toolbox:
The Windows Community Toolkit is a collection of helper functions, custom controls, and app services. It simplifies and demonstrates common developer patterns when building experiences for Windows 10.
## Build Status
| Target | Branch | Status | Recommended package version |
| ------ | ------ | ------ | ------ |
| Production | rel/6.1.0 | [![Build Status](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_apis/build/status/Toolkit-CI?branchName=rel/6.1.0)](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_build/latest?definitionId=10&branchName=rel/6.1.0) | [![NuGet](https://img.shields.io/nuget/v/Microsoft.Toolkit.Uwp.svg)](https://www.nuget.org/profiles/Microsoft.Toolkit) |
| Pre-release beta testing | master | [![Build Status](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_apis/build/status/Toolkit-CI?branchName=master)](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_build/latest?definitionId=10) | [![MyGet](https://img.shields.io/dotnet.myget/uwpcommunitytoolkit/vpre/Microsoft.Toolkit.Uwp.svg)](https://dotnet.myget.org/gallery/uwpcommunitytoolkit) |
| Pre-release beta testing | master | [![Build Status](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_apis/build/status/Toolkit-CI?branchName=master)](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_build/latest?definitionId=10) | [![DevOps](https://vsrm.dev.azure.com/dotnet/_apis/public/Release/badge/696bc9fd-f160-4e97-a1bd-7cbbb3b58f66/1/1)](https://dev.azure.com/dotnet/WindowsCommunityToolkit/_packaging?_a=feed&feed=WindowsCommunityToolkit-MainLatest) |
## Getting Started
Please read the [getting Started with the Windows Community Toolkit](https://docs.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information about using the toolkit.
## Getting Started :raised_hands:
Please read the [Getting Started with the Windows Community Toolkit](https://docs.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information about using the toolkit.
## Documentation
## Documentation :pencil:
All documentation for the toolkit is hosted on [Microsoft Docs](https://docs.microsoft.com/windows/communitytoolkit/). All API documentation can be found at the [.NET API Browser](https://docs.microsoft.com/dotnet/api/?view=win-comm-toolkit-dotnet-stable).
## Windows Community Toolkit Sample App
## Windows Community Toolkit Sample App :iphone:
Want to see the toolkit in action before jumping into the code? Download and play with the [Windows Community Toolkit Sample App](https://www.microsoft.com/store/apps/9nblggh4tlcq) from the Store.
## NuGet Packages
NuGet is a standard package manager for .NET applications which is built into Visual Studio. To open the UI, from your open solution, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution...* . Enter one of the package names below to search for it online.
## Contribution :rocket:
Do you want to contribute? Check out our [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) page to learn more about contribution and guidelines.
Once you do a search, you should see a list similar to the one below (versions may be different, but names should be the same).
## NuGet Packages :package:
NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution...* Enter one of the package names mentioned in [Windows Community Toolkit Nuget Packages](https://docs.microsoft.com/en-us/windows/communitytoolkit/nuget-packages) table to search for it online.
![nuget packages](githubresources/images/NugetPackages.png "Nuget Packages")
| NuGet Package Name | Description |
| --- | --- |
| Microsoft.Toolkit | .NET Standard NuGet package containing common code |
| Microsoft.Toolkit.HighPerformance | .NET Standard and .NET Core NuGet package with performance oriented helpers, extensions, etc. |
| Microsoft.Toolkit.Parsers | .NET Standard NuGet package containing cross-platform parsers, such as Markdown |
| Microsoft.Toolkit.Services | .NET Standard NuGet package containing cross-platform services helpers, such as LinkedIn, Microsoft Graph, Twitter and more |
| Microsoft.Toolkit.Uwp | Main NuGet package includes code only helpers such as Colors conversion tool, Storage file handling, a Stream helper class, etc. |
| Microsoft.Toolkit.Uwp.Notifications | Notifications Package - Generate tile, toast, and badge notifications for Windows 10 via code. Includes intellisense support to avoid having to use the XML syntax |
| Microsoft.Toolkit.Uwp.UI | UI Packages - XAML converters, Visual tree extensions, and other extensions and helpers for your XAML UI |
| Microsoft.Toolkit.Uwp.UI.Animations | Animations and Composition behaviors such as Blur, Fade, Rotate, etc. |
| Microsoft.Toolkit.Uwp.UI.Controls | XAML Controls such as RadialGauge, RangeSelector, etc. |
| Microsoft.Toolkit.Uwp.UI.Controls.DataGrid | XAML DataGrid control |
| Microsoft.Toolkit.Uwp.UI.Controls.Layout | XAML layout controls such as WrapLayout, StaggeredLayout, etc. |
| Microsoft.Toolkit.Uwp.UI.Lottie | Library for rendering Adobe AfterEffects animations natively in Windows apps |
| Microsoft.Toolkit.Uwp.UI.Media | Brushes, Win2D/Composition effects, and helpers to create visual effects |
| Microsoft.Toolkit.Uwp.Connectivity | API helpers such as BluetoothLEHelper and Networking |
| Microsoft.Toolkit.Uwp.DeveloperTools | XAML user controls and services to help developer building their app |
## <a name="supported"></a> Features
## <a name="supported"></a> Features :mailbox:
The [Features list](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/toc.md#controls) refers to all the currently available features that can be found in the Windows Community Toolkit. Most features should work with the October 2018 Update (1809) SDK 17763 and above; however, refer to specific documentation on each feature for more information.
## Feedback and Requests
Please use [GitHub Issues](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues) for bug reports and feature requests.
For general questions and support, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag `windows-community-toolkit`.
## <a name="dependencies"></a> Required Dependencies
The following dependencies are required for building the Windows Community Toolkit repo and sample app:
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with UWP and .NET workloads
* You'll also need to check the `C++ (v142) Universal Windows Platform tools` option under the UWP workload
* [Windows SDK April 2020 Update 19041](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive)
* [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/visual-studio-sdks)
* [.NET Framework 4.6.2 Developer Pack](https://dotnet.microsoft.com/download/visual-studio-sdks)
## Contributing
Do you want to contribute? Here are our [contribution guidelines](https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/contributing.md).
## Principles
## Principles :ballot_box_with_check:
* Principle **#1**: The toolkit will be kept simple.
* Principle **#2**: As soon as a comparable feature is available in the Windows SDK for Windows 10, it will be marked as deprecated.
* Principle **#3**: All features will be supported for two Windows SDK for Windows 10 release cycles or until another principle supersedes it.
This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/)
to clarify expected behavior in our community.
For more information see the [.NET Foundation Code of Conduct](CODE_OF_CONDUCT.md).
## Roadmap
## Roadmap :earth_americas:
Read what we [plan for next iterations](https://github.com/windows-toolkit/WindowsCommunityToolkit/milestones), and feel free to ask questions.
By adding this ([NuGet repo](https://dotnet.myget.org/F/uwpcommunitytoolkit/api/v3/index.json) | [Gallery](https://dotnet.myget.org/gallery/uwpcommunitytoolkit)) to your NuGet sources in Visual Studio, you can also get pre-release packages of upcoming versions.
## Code of Conduct :page_facing_up:
This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/)
to clarify expected behavior in our community.
For more information see the [.NET Foundation Code of Conduct](CODE_OF_CONDUCT.md).
## .NET Foundation
This project is supported by the [.NET Foundation](http://dotnetfoundation.org).