New Microsoft.Toolkit.HighPerformance package (#3128)

* Code refactoring

* Added StringExtensions tests

* Fixed test method names

* Added ArrayExtensions tests

* Removed MemoryPool<T>.Resize extension

* Added ArrayPoolExtensions tests

* Added SpinLockExtensions tests

* Moved HashCode<T> class to Helpers namespace

* Added ByReference<T> tests

* Changed visibility of one constructor

* Improved ByReference<T> tests

* Added ReadOnlyByReference<T> tests

* Fixed a small build error

* Added new List<T> extensions

* Added List<T> extensions tests

* Fixed a parameter name

* Fixed incorrect XML docs

* Added missing List<T> extension APIs

* Disabled warning

* Removed unnecessary type constraint

* Bug fixes to some List<T> extensions

* Improved some XML docs

* Added List<T>.DangerousAsSpan extension

* Added more unit tests

* Fixed a bug in the List<T>.DangerousAsSpan extension

* Minor tweaks to some docs

* Removed List<T> extensions (too hacky)

* Minor code tweaks (just in case)

* Fixed incorrect API visibility

* Added tests for the HashCode<T> type

* Added .NET Core 3.0 tests for HashCode<T>

* Added ParallelHelper.For tests

* Code refactoring

* Added ParallelHelper.For2D tests

* Fixed empty condition check for 2D loops

* Added ParallelHelper.ForEach in tests

* Added ParallelHelper.ForEach ref tests

* Fixed ParallelHelper.For2D tests

* Improved ParallelHelper.For/2D tests

* Switched HighPerformance tests to shared project

* Renamed HighPerformance.NetCore project

* Moved HighPerformance tests to subfolder

* Fixed incorrect namespaces

* Added empty HighPerformance.UWP test project

* Fixed shared project for UWP test project

* Added missing Unsafe NuGet package to UWP project

* Switched compile tile directives in HighPerformance project

* Added missing file headers

* Minor code refactoring to HashCode<T>

* Minor performance improvements

* Fixed HashCode<T>.Combine API for > int.MaxValue byte sizes

* Improved HashCode<T>.Combine performance on small spans

* Speed improvements in the GetDjb2HashCode<T> API

* Refactored HashCode<T> to facilitate extensions

* Fixed incorrect namespace

* Added HashCodeExtensions class

* Added HashCodeExtensions tests

* Minor code style tweaks

* Added SpanEnumerable<T> type

* Updated T[] and Span<T> Enumerate extensions

* Added SpanExtensions tests

* Fixed an issue in the DJB2 hash method

* Minor code refactoring

* Added EditorBrowsable attributes to enumerator types

* Added MemoryOwner<T> type

* Minor code refactoring

* More code refactoring

* Added MemoryOwner<T> Empty and Length properties

* Added missing header text

* Fixed a refactoring typo

* Added MemoryOwner<T> tests

* Minor code refactoring

* Fixed MemoryOwner<T> XML docs

* Minor optimization to GetDjb2HashCode

* Minor optimization to HashCode<T>.CombineValues

* Minor optimization to Count extension with managed types

* Added a Count test for the managed path

* Added BoolExtensions.ToInt API

* Removed automatically added rules

* Fixed incorrect XML docs

* Added MemoryOwner<T>.DangerousGetReference API

* Added extensions for some MemoryMarshal APIs

* Minor code tweaks

* Added MemoryOwner<T> method

* Added more XML comments

* Fixed an incorrect method prototype

* Code refactoring, added AllocationMode enum

* Added SpanOwner<T> type

* Added SpanOwner<T> tests

* Removed unnecessary check

* Removed unnecessary package reference on .NET Standard 2.1

* Improved tests for ReadOnlySpan<T>.Count

* Fixed a bug in ReadOnlySpan<T>.Count with managed types

* Updated two type constraints

* Minor optimizations to ParallelHelper

* Minor code tweaks

* Removed unnecessary APIs

* Added UInt32Extensions class

* Added unit tests for the UInt32Extensions class

* Fixed some comments

* Added some remarks to the new uint APIs

* Minor optimization

* Added UInt64Extensions class

* Added unit tests for UInt64Extensions type

* Fixed some typos and comments

* Fixed a unit test

* Minor performance improvements

* Added ToString() override and debug display to MemoryOwner<T>

* Added ToString() override and debug display to SpanOwner<T>

* Added MemoryOwner<T> debug proxy type

* Added SpanOwner<T> debug proxy type

* Added missing using directive

* Added info to the .csproj file

* Removed two APIs, for additional safety

* Refactored bit helpers into a separate class, added by ref overloads

* Minor code tweaks

* Added Box<T> type

* Added tests for the Box<T> type

* Added Box<T> object schema

* Added Box<T>.GetFrom(object) API

* Added more Box<T> tests, added more comments

* Fixed a copy paste fail

* Added ValueTypeExtensions type

* Added ValueTypeExtensions tests

* Added missing GC.SuppressFinalize call in MemoryOwner<T>

* Initial implementation of MemoryStream

* Implemented MemoryStream.Seek method

* Implemented ReadByte and WriteByte methods

* Added Memory<T> extension to create a Stream

* Implemented MemoryStream.Dispose method

* Fixed typos in a test class

* Added ReadAsync overrides

* Added WriteAsync overrides

* Code refactoring

* Added more .NET Standard 2.1 overrides

* Moved CopyToAsync method to .NET Standard 2.0

* Moved FlushAsync, reordered methods

* Added [ReadOnly]Memory<T> extension tests

* Added initial stream tests

* Fixed a bug in MemoryStream.Read

* Added unit tests for the MemoryStream type

* Added MemoryStream.[Read|Write]Byte tests, minor tweaks

* Updated .sln file

* Added IMemoryOwnerStream type

* Added IMemoryOwnerExtensions.AsStream type

* Minor tweaks to some XML comments

* Added IMemoryOwner extensions and stream tests

* Fixed an incorrect namespace

* Aadded ArrayPoolBufferWriter<T> type

* Minor code refactoring

* Added debug view and ToString override for ArrayPoolBufferWriter<T>

* Minor code refactoring

* Added destructor to ArrayPoolBufferWriter<T> type

* Added tests for ArrayPoolBufferWriter<T>

* Fixed some copy-paste fails

* Added IMemoryOwner<T> interface to ArrayPoolBufferWriter<T>

* Updated .csproj description

* Fixed some comments

* Updated sln

* Minor code style tweak

* Minor optimization, code style tweaks

* Fixed some tests not running on .NET Core 3.0

* Added initial Array2DExtensions type

* Added T[,].AsSpan extension

* Added Count<T> and Djb2 hashcode extensions for T[,] arrays

* Added tests for the 2D array extensions

* Code refactoring

* Added NullableByReference<T> type

* Minor code refactoring

* Added NullableReadOnlyByReference<T> type

* Added unit tests for new Nullable[ReadOnly]ByReference<T> types

* Fixed an XML comment

* Added T[,] array GetRow and GetColumn extensions

* Added Array2DColumnEnumerable<T>.ToArray() helper method

* Code refactoring

* Added T[,].GetRow support on .NET Standard 2.0

* Added and optimized T[,].Fill extension

* Added T[,].Fill tests

* Added tests for T[,].GetRow extension

* Bug fixes in the 2D array enumerators

* Added tests for T[,].GetColumn extension

* Fixed a typo

* Fixed duplicate using directives

* Fixed an XML comment

* Inverted precompiler conditional directive for clarity

* Fixed error in an XML comment

* Removed unnecessary using directive

* Fixed publisher name in UWP test project

* Added StyleCop.Analyzers package to UWP test project

* Resolved StyleCop warnings in unit tests

* Added StyleCop.Analyzers package to .NET Core test project

* Resolved .NET Core specific StyleCop warnings

* Updated Test SDK and packages in .NET Core unit test project

* Fixed object name in MemoryStream.ThrowObjectDisposedException()

* Minor code style tweak and optimization

* Added test for the argument name in ParallelHelper exceptions

* Fixed visibility modifiers

* Fixed Box<T> type on ARM devices

* Added MemoryStream tests for argument names in exceptions

* Minor improvements to some unit tests

* Removed duplicate System.Runtime.CompilerServices.Unsafe reference

* Removed unnecessary StyleCop reference and .ruleset file

* Resolved StyleCop warnings in unit tests

* Minor performance improvements in ByReference<T> types on x64

* Minor style tweaks

* Fixed missed refactoring

* Code refactoring

* Added missing readonly struct modifiers

* Enabgled running HighPerformance tests on build.

(cherry picked from commit c5524125391ca208586d0de09fffb89e3a01264d)

* Bug fixes in the Box<T> type on some runtimes

* Added missing "Pack" target in UWP test project

* Code refactoring, moved Box<T> extensions to same file for clarity

* Bumped System.Runtime.CompilerServices.Unsafe to 5.0.0-preview.2.20160.6

* Fixed Box<T>.ToString/GetHashCode issues on .NET Core 2.1 Release

* Updated comments on the Box<T>.GetReference() method

Also pushing this commit just to re-trigger the CI build, since GitHub went offline and caused the previous one to fail/half midway through

* Fixed package downgrade in UWP test project

* Aligned ref returns for ReadOnlySpan<T>/string to MemoryMarsha.GetReference

* Renamed ByReference<T> APIs

* Removed unnecessary preview package

Co-authored-by: Michael Hawker MSFT (XAML Llama) <michael.hawker@outlook.com>
Co-authored-by: Alexandre Zollinger Chohfi <alzollin@microsoft.com>
This commit is contained in:
Sergio Pedri 2020-05-06 01:44:11 +02:00 коммит произвёл GitHub
Родитель 823a2cca60
Коммит c65d648695
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
95 изменённых файлов: 10088 добавлений и 5 удалений

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

@ -0,0 +1,219 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance
{
/// <summary>
/// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
/// </summary>
/// <typeparam name="T">The type of value being boxed.</typeparam>
[DebuggerDisplay("{ToString(),raw}")]
public sealed class Box<T>
where T : struct
{
// Boxed value types in the CLR are represented in memory as simple objects that store the method table of
// the corresponding T value type being boxed, and then the data of the value being boxed:
// [ sync block || pMethodTable || boxed T value ]
// ^ ^
// | \-- Unsafe.Unbox<T>(Box<T>)
// \-- Box<T> reference
// For more info, see: https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/.
// Note that there might be some padding before the actual data representing the boxed value,
// which might depend on both the runtime and the exact CPU architecture.
// This is automatically handled by the unbox !!T instruction in IL, which
// unboxes a given value type T and returns a reference to its boxed data.
/// <summary>
/// Initializes a new instance of the <see cref="Box{T}"/> class.
/// </summary>
/// <remarks>
/// This constructor is never used, it is only declared in order to mark it with
/// the <see langword="private"/> visibility modifier and prevent direct use.
/// </remarks>
private Box()
{
}
/// <summary>
/// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
/// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box<T> GetFrom(object obj)
{
if (obj.GetType() != typeof(T))
{
ThrowInvalidCastExceptionForGetFrom();
}
return Unsafe.As<Box<T>>(obj);
}
/// <summary>
/// Returns a <see cref="Box{T}"/> reference from the input <see cref="object"/> instance.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
/// <returns>A <see cref="Box{T}"/> reference pointing to <paramref name="obj"/>.</returns>
/// <remarks>
/// This method doesn't check the actual type of <paramref name="obj"/>, so it is responsability of the caller
/// to ensure it actually represents a boxed <typeparamref name="T"/> value and not some other instance.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box<T> DangerousGetFrom(object obj)
{
return Unsafe.As<Box<T>>(obj);
}
/// <summary>
/// Tries to get a <see cref="Box{T}"/> reference from an input <see cref="object"/> representing a boxed <typeparamref name="T"/> value.
/// </summary>
/// <param name="obj">The input <see cref="object"/> instance to check.</param>
/// <param name="box">The resulting <see cref="Box{T}"/> reference, if <paramref name="obj"/> was a boxed <typeparamref name="T"/> value.</param>
/// <returns><see langword="true"/> if a <see cref="Box{T}"/> instance was retrieved correctly, <see langword="false"/> otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1115", Justification = "Comment for [NotNullWhen] attribute")]
public static bool TryGetFrom(
object obj,
#if NETSTANDARD2_1
/* On .NET Standard 2.1, we can add the [NotNullWhen] attribute
* to let the code analysis engine know that whenever this method
* returns true, box will always be assigned to a non-null value.
* This will eliminate the null warnings when in a branch that
* is only taken when this method returns true. */
[NotNullWhen(true)]
#endif
out Box<T>? box)
{
if (obj.GetType() == typeof(T))
{
box = Unsafe.As<Box<T>>(obj);
return true;
}
box = null;
return false;
}
/// <summary>
/// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="Box{T}"/> instance.
/// </summary>
/// <param name="box">The input <see cref="Box{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(Box<T> box)
{
return Unsafe.Unbox<T>(box);
}
/// <summary>
/// Implicitly creates a new <see cref="Box{T}"/> instance from a given <typeparamref name="T"/> value.
/// </summary>
/// <param name="value">The input <typeparamref name="T"/> value to wrap.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Box<T>(T value)
{
/* The Box<T> type is never actually instantiated.
* Here we are just boxing the input T value, and then reinterpreting
* that object reference as a Box<T> reference. As such, the Box<T>
* type is really only used as an interface to access the contents
* of a boxed value type. This also makes it so that additional methods
* like ToString() or GetHashCode() will automatically be referenced from
* the method table of the boxed object, meaning that they don't need to
* manually be implemented in the Box<T> type. For instance, boxing a float
* and calling ToString() on it directly, on its boxed object or on a Box<T>
* reference retrieved from it will produce the same result in all cases. */
return Unsafe.As<Box<T>>(value);
}
/// <inheritdoc/>
public override string ToString()
{
/* Here we're overriding the base object virtual methods to ensure
* calls to those methods have a correct results on all runtimes.
* For instance, not doing so is causing issue on .NET Core 2.1 Release
* due to how the runtime handles the Box<T> reference to an actual
* boxed T value (not a concrete Box<T> instance as it would expect).
* To fix that, the overrides will simply call the expected methods
* directly on the boxed T values. These methods will be directly
* invoked by the JIT compiler when using a Box<T> reference. When
* an object reference is used instead, the call would be forwarded
* to those same methods anyway, since the method table for an object
* representing a T instance is the one of type T anyway. */
return this.GetReference().ToString();
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return Equals(this, obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetReference().GetHashCode();
}
/// <summary>
/// Throws an <see cref="InvalidCastException"/> when a cast from an invalid <see cref="object"/> is attempted.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidCastExceptionForGetFrom()
{
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");
}
}
/// <summary>
/// Helpers for working with the <see cref="Box{T}"/> type.
/// </summary>
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402", Justification = "Extension class to replace instance methods for Box<T>")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Extensions being declared after the type they apply to")]
public static class BoxExtensions
{
/// <summary>
/// Gets a <typeparamref name="T"/> reference from a <see cref="Box{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
/// <param name="box">The input <see cref="Box{T}"/> instance.</param>
/// <returns>A <typeparamref name="T"/> reference to the boxed value within <paramref name="box"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference<T>(this Box<T> box)
where T : struct
{
/* The reason why this method is an extension and is not part of
* the Box<T> type itself is that Box<T> is really just a mask
* used over object references, but it is never actually instantiated.
* Because of this, the method table of the objects in the heap will
* be the one of type T created by the runtime, and not the one of
* the Box<T> type. To avoid potential issues when invoking this method
* on different runtimes, which might handle that scenario differently,
* we use an extension method, which is just syntactic sugar for a static
* method belonging to another class. This isn't technically necessary,
* but it's just an extra precaution since the syntax for users remains
* exactly the same anyway. Here we just call the Unsafe.Unbox<T>(object)
* API, which is hidden away for users of the type for simplicity.
* Note that this API will always actually involve a conditional
* branch, which is introduced by the JIT compiler to validate the
* object instance being unboxed. But since the alternative of
* manually tracking the offset to the boxed data would be both
* more error prone, and it would still introduce some overhead,
* this doesn't really matter in this case anyway. */
return ref Unsafe.Unbox<T>(box);
}
}
}

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

@ -0,0 +1,353 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
/// <summary>
/// Represents a heap-based, array-backed output sink into which <typeparamref name="T"/> data can be written.
/// </summary>
/// <typeparam name="T">The type of items to write to the current instance.</typeparam>
/// <remarks>
/// This is a custom <see cref="IBufferWriter{T}"/> implementation that replicates the
/// functionality and API surface of the array-based buffer writer available in
/// .NET Standard 2.1, with the main difference being the fact that in this case
/// the arrays in use are rented from the shared <see cref="ArrayPool{T}"/> instance,
/// and that <see cref="ArrayPoolBufferWriter{T}"/> is also available on .NET Standard 2.0.
/// </remarks>
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IMemoryOwner<T>
{
/// <summary>
/// The default buffer size to use to expand empty arrays.
/// </summary>
private const int DefaultInitialBufferSize = 256;
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T[]? array;
#pragma warning disable IDE0032
/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private int index;
#pragma warning restore IDE0032
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
public ArrayPoolBufferWriter()
{
/* Since we're using pooled arrays, we can rent the buffer with the
* default size immediately, we don't need to use lazy initialization
* to save unnecessary memory allocations in this case. */
this.array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize);
this.index = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
/// </exception>
public ArrayPoolBufferWriter(int initialCapacity)
{
if (initialCapacity <= 0)
{
ThrowArgumentOutOfRangeExceptionForInitialCapacity();
}
this.array = ArrayPool<T>.Shared.Rent(initialCapacity);
this.index = 0;
}
/// <summary>
/// Finalizes an instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
/// </summary>
~ArrayPoolBufferWriter() => this.Dispose();
/// <inheritdoc/>
Memory<T> IMemoryOwner<T>.Memory
{
/* This property is explicitly implemented so that it's hidden
* under normal usage, as the name could be confusing when
* displayed besides WrittenMemory and GetMemory().
* The IMemoryOwner<T> interface is implemented primarily
* so that the AsStream() extension can be used on this type,
* allowing users to first create a ArrayPoolBufferWriter<byte>
* instance to write data to, then get a stream through the
* extension and let it take care of returning the underlying
* buffer to the shared pool when it's no longer necessary.
* Inlining is not needed here since this will always be a callvirt. */
get => MemoryMarshal.AsMemory(WrittenMemory);
}
/// <summary>
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
/// </summary>
public ReadOnlyMemory<T> WrittenMemory
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return array!.AsMemory(0, this.index);
}
}
/// <summary>
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
/// </summary>
public ReadOnlySpan<T> WrittenSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return array!.AsSpan(0, this.index);
}
}
/// <summary>
/// Gets the amount of data written to the underlying buffer so far.
/// </summary>
public int WrittenCount
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.index;
}
/// <summary>
/// Gets the total amount of space within the underlying buffer.
/// </summary>
public int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return array!.Length;
}
}
/// <summary>
/// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
/// </summary>
public int FreeCapacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return array!.Length - this.index;
}
}
/// <summary>
/// Clears the data written to the underlying buffer.
/// </summary>
/// <remarks>
/// You must clear the <see cref="ArrayPoolBufferWriter{T}"/> before trying to re-use it.
/// </remarks>
public void Clear()
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
array.AsSpan(0, this.index).Clear();
this.index = 0;
}
/// <inheritdoc/>
public void Advance(int count)
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
if (count < 0)
{
ThrowArgumentOutOfRangeExceptionForNegativeCount();
}
if (this.index > array!.Length - count)
{
ThrowArgumentExceptionForAdvancedTooFar();
}
this.index += count;
}
/// <inheritdoc/>
public Memory<T> GetMemory(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
return this.array.AsMemory(this.index);
}
/// <inheritdoc/>
public Span<T> GetSpan(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
return this.array.AsSpan(this.index);
}
/// <summary>
/// Ensures that <see cref="array"/> has enough free space to contain a given number of new items.
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckAndResizeBuffer(int sizeHint)
{
if (this.array is null)
{
ThrowObjectDisposedException();
}
if (sizeHint < 0)
{
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
}
if (sizeHint == 0)
{
sizeHint = 1;
}
if (sizeHint > FreeCapacity)
{
int minimumSize = this.index + sizeHint;
ArrayPool<T>.Shared.Resize(ref this.array, minimumSize);
}
}
/// <inheritdoc/>
public void Dispose()
{
T[]? array = this.array;
if (array is null)
{
return;
}
GC.SuppressFinalize(this);
this.array = null;
ArrayPool<T>.Shared.Return(array);
}
/// <inheritdoc/>
[Pure]
public override string ToString()
{
// See comments in MemoryOwner<T> about this
if (typeof(T) == typeof(char) &&
this.array is char[] chars)
{
return new string(chars, 0, this.index);
}
// Same representation used in Span<T>
return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the initial capacity is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity()
{
throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
{
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
{
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForAdvancedTooFar()
{
throw new ArgumentException("The buffer writer has advanced too far");
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException("The current buffer has already been disposed");
}
}
}

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

@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
/// <summary>
/// An <see langword="enum"/> that indicates a mode to use when allocating buffers.
/// </summary>
public enum AllocationMode
{
/// <summary>
/// The default allocation mode for pooled memory (rented buffers are not cleared).
/// </summary>
Default,
/// <summary>
/// Clear pooled buffers when renting them.
/// </summary>
Clear
}
}

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

@ -0,0 +1,282 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
/// <summary>
/// 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<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryOwner<T> : IMemoryOwner<T>
{
/// <summary>
/// The starting offset within <see cref="array"/>.
/// </summary>
private readonly int start;
#pragma warning disable IDE0032
/// <summary>
/// The usable length within <see cref="array"/> (starting from <see cref="start"/>).
/// </summary>
private readonly int length;
#pragma warning restore IDE0032
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T[]? array;
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
private MemoryOwner(int length, AllocationMode mode)
{
this.start = 0;
this.length = length;
this.array = ArrayPool<T>.Shared.Rent(length);
if (mode == AllocationMode.Clear)
{
this.array.AsSpan(0, length).Clear();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
/// <param name="start">The starting offset within <paramref name="array"/>.</param>
/// <param name="length">The length of the array to use.</param>
private MemoryOwner(T[] array, int start, int length)
{
this.start = start;
this.length = length;
this.array = array;
}
/// <summary>
/// Finalizes an instance of the <see cref="MemoryOwner{T}"/> class.
/// </summary>
~MemoryOwner() => this.Dispose();
/// <summary>
/// Gets an empty <see cref="MemoryOwner{T}"/> instance.
/// </summary>
[Pure]
public static MemoryOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new MemoryOwner<T>(0, AllocationMode.Default);
}
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size) => new MemoryOwner<T>(size, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new MemoryOwner<T>(size, mode);
/// <summary>
/// Gets the number of items in the current instance
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.length;
}
/// <inheritdoc/>
public Memory<T> Memory
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return new Memory<T>(array!, this.start, this.length);
}
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return new Span<T>(array!, this.start, this.length);
}
}
/// <summary>
/// Returns a reference to the first element within the current instance, with no bounds check.
/// </summary>
/// <returns>A reference to the first element within the current instance.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
/// <remarks>
/// This method does not perform bounds checks on the underlying buffer, but does check whether
/// the buffer itself has been disposed or not. This check should not be removed, and it's also
/// the reason why the method to get a reference at a specified offset is not present.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReference()
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
return ref array!.DangerousGetReferenceAt(this.start);
}
/// <summary>
/// Slices the buffer currently in use and returns a new <see cref="MemoryOwner{T}"/> instance.
/// </summary>
/// <param name="start">The starting offset within the current buffer.</param>
/// <param name="length">The length of the buffer to use.</param>
/// <returns>A new <see cref="MemoryOwner{T}"/> instance using the target range of items.</returns>
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="start"/> or <paramref name="length"/> are not valid.</exception>
/// <remarks>
/// Using this method will dispose the current instance, and should only be used when an oversized
/// buffer is rented and then adjusted in size, to avoid having to rent a new buffer of the new
/// size and copy the previous items into the new one, or needing an additional variable/field
/// to manually handle to track the used range within a given <see cref="MemoryOwner{T}"/> instance.
/// </remarks>
[Pure]
public MemoryOwner<T> Slice(int start, int length)
{
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
this.array = null;
if ((uint)start > this.length)
{
ThrowInvalidOffsetException();
}
if ((uint)length > (this.length - start))
{
ThrowInvalidLengthException();
}
return new MemoryOwner<T>(array!, start, length);
}
/// <inheritdoc/>
public void Dispose()
{
T[]? array = this.array;
if (array is null)
{
return;
}
GC.SuppressFinalize(this);
this.array = null;
ArrayPool<T>.Shared.Return(array);
}
/// <inheritdoc/>
[Pure]
public override string ToString()
{
/* Normally we would throw if the array has been disposed,
* but in this case we'll just return the non formatted
* representation as a fallback, since the ToString method
* is generally expected not to throw exceptions. */
if (typeof(T) == typeof(char) &&
this.array is char[] chars)
{
return new string(chars, this.start, this.length);
}
// Same representation used in Span<T>
return $"Microsoft.Toolkit.HighPerformance.Buffers.MemoryOwner<{typeof(T)}>[{this.length}]";
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204", Justification = "Exception throwers at the end of class")]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The current buffer has already been disposed");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="start"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidOffsetException()
{
throw new ArgumentOutOfRangeException(nameof(start), "The input start parameter was not valid");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="length"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidLengthException()
{
throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid");
}
}
}

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

@ -0,0 +1,155 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
/// <summary>
/// A stack-only type with the ability to rent a buffer of a specified length and getting a <see cref="Span{T}"/> from it.
/// This type mirrors <see cref="MemoryOwner{T}"/> but without allocations and with further optimizations.
/// As this is a stack-only type, it relies on the duck-typed <see cref="IDisposable"/> pattern introduced with C# 8.
/// It should be used like so:
/// <code>
/// using (SpanOwner&lt;byte> buffer = SpanOwner&lt;byte>.Allocate(1024))
/// {
/// // Use the buffer here...
/// }
/// </code>
/// As soon as the code leaves the scope of that <see langword="using"/> block, the underlying buffer will automatically
/// be disposed. The APIs in <see cref="SpanOwner{T}"/> rely on this pattern for extra performance, eg. they don't perform
/// the additional checks that are done in <see cref="MemoryOwner{T}"/> to ensure that the buffer hasn't been disposed
/// before returning a <see cref="Memory{T}"/> or <see cref="Span{T}"/> instance from it.
/// As such, this type should always be used with a <see langword="using"/> block or expression.
/// 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<>))]
[DebuggerDisplay("{ToString(),raw}")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct SpanOwner<T>
{
#pragma warning disable IDE0032
/// <summary>
/// The usable length within <see cref="array"/>.
/// </summary>
private readonly int length;
#pragma warning restore IDE0032
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private readonly T[] array;
/// <summary>
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
private SpanOwner(int length, AllocationMode mode)
{
this.length = length;
this.array = ArrayPool<T>.Shared.Rent(length);
if (mode == AllocationMode.Clear)
{
this.array.AsSpan(0, length).Clear();
}
}
/// <summary>
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
/// </summary>
[Pure]
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SpanOwner<T>(0, AllocationMode.Default);
}
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="size">The length of the new memory buffer to use.</param>
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, mode);
/// <summary>
/// Gets the number of items in the current instance
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.length;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Span<T>(array, 0, this.length);
}
/// <summary>
/// Returns a reference to the first element within the current instance, with no bounds check.
/// </summary>
/// <returns>A reference to the first element within the current instance.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReference()
{
return ref array.DangerousGetReference();
}
/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
ArrayPool<T>.Shared.Return(array);
}
/// <inheritdoc/>
[Pure]
public override string ToString()
{
if (typeof(T) == typeof(char) &&
this.array is char[] chars)
{
return new string(chars, 0, this.length);
}
// Same representation used in Span<T>
return $"Microsoft.Toolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
}
}
}

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

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <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; }
}
}

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

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <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; }
}
}

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

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <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; }
}
}

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

@ -0,0 +1,163 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <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>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[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];
for (int i = 0; i < height; i++)
{
array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(i, this.column);
}
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 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;
/// <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();
}
this.array = array;
this.column = column;
this.height = array.GetLength(0);
this.row = -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 row = this.row + 1;
if (row < this.height)
{
this.row = row;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this.array.DangerousGetReferenceAt(this.row, this.column);
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
{
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
}
}
}

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

@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETSTANDARD2_0
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <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>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[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 => ref this.array.DangerousGetReferenceAt(this.row, this.column);
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
{
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
}
}
}
#endif

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

@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct ReadOnlySpanEnumerable<T>
{
/// <summary>
/// The source <see cref="ReadOnlySpan{T}"/> instance
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlySpanEnumerable{T}"/> struct.
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanEnumerable(ReadOnlySpan<T> span)
{
this.span = span;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.span);
/// <summary>
/// An enumerator for a source <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// The current index within <see cref="span"/>.
/// </summary>
private int index;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(ReadOnlySpan<T> span)
{
this.span = span;
this.index = -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 newIndex = this.index + 1;
if (newIndex < this.span.Length)
{
this.index = newIndex;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008", Justification = "ValueTuple<T1,T2> return type")]
public (int Index, T Value) Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
int currentIndex = this.index;
T value = Unsafe.Add(ref MemoryMarshal.GetReference(this.span), currentIndex);
return (currentIndex, value);
}
}
}
}
}

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

@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct ReadOnlySpanTokenizer<T>
where T : IEquatable<T>
{
/// <summary>
/// The source <see cref="ReadOnlySpan{T}"/> instance
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// The separator <typeparamref name="T"/> item to use.
/// </summary>
private readonly T separator;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlySpanTokenizer{T}"/> struct.
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator)
{
this.span = span;
this.separator = separator;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.span, this.separator);
/// <summary>
/// An enumerator for a source <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// The separator item to use.
/// </summary>
private readonly T separator;
/// <summary>
/// The current initial offset.
/// </summary>
private int start;
/// <summary>
/// The current final offset.
/// </summary>
private int end;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
public Enumerator(ReadOnlySpan<T> span, T separator)
{
this.span = span;
this.separator = separator;
this.start = 0;
this.end = -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
newEnd = this.end + 1,
length = this.span.Length;
// Additional check if the separator is not the last character
if (newEnd <= length)
{
this.start = newEnd;
int index = this.span.Slice(newEnd).IndexOf(this.separator);
// Extract the current subsequence
if (index >= 0)
{
this.end = newEnd + index;
return true;
}
this.end = length;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public ReadOnlySpan<T> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.span.Slice(this.start, this.end - this.start);
}
}
}
}

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

@ -0,0 +1,193 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct SpanEnumerable<T>
{
/// <summary>
/// The source <see cref="Span{T}"/> instance
/// </summary>
private readonly Span<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="SpanEnumerable{T}"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanEnumerable(Span<T> span)
{
this.span = span;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.span);
/// <summary>
/// An enumerator for a source <see cref="Span{T}"/> instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source <see cref="Span{T}"/> instance.
/// </summary>
private readonly Span<T> span;
/// <summary>
/// The current index within <see cref="span"/>.
/// </summary>
private int index;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(Span<T> span)
{
this.span = span;
this.index = -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 newIndex = this.index + 1;
if (newIndex < this.span.Length)
{
this.index = newIndex;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public Item Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
/* On .NET Standard 2.1 we can save 4 bytes by piggybacking
* the current index in the length of the wrapped span.
* We're going to use the first item as the target reference,
* and the length as a host for the current original offset.
* This is not possible on .NET Standard 2.1 as we lack
* the API to create spans from arbitrary references. */
return new Item(ref ri, this.index);
#else
return new Item(this.span, this.index);
#endif
}
}
}
/// <summary>
/// An item from a source <see cref="Span{T}"/> instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct Item
{
/// <summary>
/// The source <see cref="Span{T}"/> instance.
/// </summary>
private readonly Span<T> span;
#if NETSTANDARD2_1
/// <summary>
/// Initializes a new instance of the <see cref="Item"/> struct.
/// </summary>
/// <param name="value">A reference to the target value.</param>
/// <param name="index">The index of the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Item(ref T value, int index)
{
this.span = MemoryMarshal.CreateSpan(ref value, index);
}
#else
/// <summary>
/// The current index within <see cref="span"/>.
/// </summary>
private readonly int index;
/// <summary>
/// Initializes a new instance of the <see cref="Item"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
/// <param name="index">The current index within <paramref name="span"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Item(Span<T> span, int index)
{
this.span = span;
this.index = index;
}
#endif
/// <summary>
/// Gets the reference to the current value.
/// </summary>
public ref T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
return ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
return ref ri;
#endif
}
}
/// <summary>
/// Gets the current index.
/// </summary>
public int Index
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
return this.span.Length;
#else
return this.index;
#endif
}
}
}
}
}

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

@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct SpanTokenizer<T>
where T : IEquatable<T>
{
/// <summary>
/// The source <see cref="Span{T}"/> instance
/// </summary>
private readonly Span<T> span;
/// <summary>
/// The separator <typeparamref name="T"/> item to use.
/// </summary>
private readonly T separator;
/// <summary>
/// Initializes a new instance of the <see cref="SpanTokenizer{T}"/> struct.
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanTokenizer(Span<T> span, T separator)
{
this.span = span;
this.separator = separator;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.span, this.separator);
/// <summary>
/// An enumerator for a source <see cref="Span{T}"/> instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source <see cref="Span{T}"/> instance.
/// </summary>
private readonly Span<T> span;
/// <summary>
/// The separator item to use.
/// </summary>
private readonly T separator;
/// <summary>
/// The current initial offset.
/// </summary>
private int start;
/// <summary>
/// The current final offset.
/// </summary>
private int end;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
public Enumerator(Span<T> span, T separator)
{
this.span = span;
this.separator = separator;
this.start = 0;
this.end = -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
newEnd = this.end + 1,
length = this.span.Length;
// Additional check if the separator is not the last character
if (newEnd <= length)
{
this.start = newEnd;
int index = this.span.Slice(newEnd).IndexOf(this.separator);
// Extract the current subsequence
if (index >= 0)
{
this.end = newEnd + index;
return true;
}
this.end = length;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public Span<T> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.span.Slice(this.start, this.end - this.start);
}
}
}
}

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

@ -0,0 +1,260 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <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 2D <typeparamref name="T"/> array, with no bounds checks.
/// </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>
/// <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)
{
var arrayData = Unsafe.As<RawArray2DData>(array);
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
}
/// <summary>
/// Returns a reference to an element at a specified coordinate within a given 2D <typeparamref name="T"/> array, with no bounds checks.
/// </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="i">The vertical index of the element to retrieve within <paramref name="array"/>.</param>
/// <param name="j">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)
{
var arrayData = Unsafe.As<RawArray2DData>(array);
int offset = (i * arrayData.Width) + j;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
}
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
// CLR 2D arrays are laid out in memory as follows:
// [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ]
// ^ ^
// | \-- ref Unsafe.As<RawArray2DData>(array).Data
// \-- array
// The length is always padded to IntPtr just like with SZ arrays.
// The total data padding is therefore 20 bytes on x86 (4 + 4 + 4 + 4 + 4), or 24 bytes on x64.
[StructLayout(LayoutKind.Sequential)]
private sealed class RawArray2DData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
public IntPtr Length;
public int Height;
public int Width;
public int HeightLowerBound;
public int WidthLowerBound;
public byte Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
/// <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 NETSTANDARD2_1
ref T r0 = ref array[i, bounds.Left];
// Span<T>.Fill will use vectorized instructions when possible
MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
#else
ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
for (int j = 0; j < bounds.Width; j++)
{
/* Storing the initial reference and only incrementing
* that one in each iteration saves one additional indirect
* dereference for every loop iteration compared to using
* the DangerousGetReferenceAt<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.
/// </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>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static
#if NETSTANDARD2_1
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)
{
if ((uint)row >= (uint)array.GetLength(0))
{
throw new ArgumentOutOfRangeException(nameof(row));
}
#if NETSTANDARD2_1
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
#else
return new Array2DRowEnumerable<T>(array, row);
#endif
}
/// <summary>
/// Returns an enumerable 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 =
/// {
/// { 1, 2, 3 },
/// { 4, 5, 6 },
/// { 7, 8, 9 }
/// };
///
/// foreach (ref int number in matrix.GetColumn(1))
/// {
/// // Access the current number by reference here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </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="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>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Array2DColumnEnumerable<T> GetColumn<T>(this T[,] array, int column)
{
return new Array2DColumnEnumerable<T>(array, column);
}
#if NETSTANDARD2_1
/// <summary>
/// Cretes a new <see cref="Span{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="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[,] array)
{
var arrayData = Unsafe.As<RawArray2DData>(array);
/* On x64, the length is padded to x64, but it is represented in memory
* as two sequential uint fields (one of which is padding).
* So we can just reinterpret a reference to the IntPtr as one of type
* uint, to access the first 4 bytes of that field, regardless of whether
* we're running in a 32 or 64 bit process. This will work when on little
* endian systems as well, as the memory layout for fields is the same,
* the only difference is the order of bytes within each field of a given type.
* We use checked here to follow suit with the CoreCLR source, where an
* invalid value here should fail to perform the cast and throw an exception. */
int length = checked((int)Unsafe.As<IntPtr, uint>(ref arrayData.Length));
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return MemoryMarshal.CreateSpan(ref r0, length);
}
/// <summary>
/// Counts the number of occurrences of a given value into a target 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <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>
{
return ReadOnlySpanExtensions.Count(array.AsSpan(), value);
}
/// <summary>
/// Gets a content hash from the input 2D <typeparamref name="T"/> array instance using the Djb2 algorithm.
/// </summary>
/// <typeparam name="T">The type of items in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>The Djb2 value for the input 2D <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
{
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(array.AsSpan());
}
#endif
}
}

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

@ -0,0 +1,158 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <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 <typeparamref name="T"/> array, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <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)
{
var arrayData = Unsafe.As<RawArrayData>(array);
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <typeparamref name="T"/> array, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="array"/>.</param>
/// <returns>A reference to the element within <paramref name="array"/> 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 T[] array, int i)
{
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, i);
return ref ri;
}
// Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
// [ sync block || pMethodTable || num components || MD array bounds || array data .. ]
// ^ ^ ^ returned reference
// | \-- ref Unsafe.As<RawArrayData>(array).Data
// \-- array
// The base size of an array includes all the fields before the array data,
// including the sync block and method table. The reference to RawData.Data
// points at the number of components, skipping over these two pointer-sized fields.
[StructLayout(LayoutKind.Sequential)]
private sealed class RawArrayData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
public IntPtr Length;
public byte Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
/// <summary>
/// Counts the number of occurrences of a given value into a target <typeparamref name="T"/> array instance.
/// </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>
/// <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>
{
return ReadOnlySpanExtensions.Count(array, value);
}
/// <summary>
/// Enumerates the items in the input <typeparamref name="T"/> array instance, as pairs of reference/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// int[] numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in numbers.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="array">The source <typeparamref name="T"/> array to enumerate.</param>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this T[] array)
{
return new SpanEnumerable<T>(array);
}
/// <summary>
/// Tokenizes the values in the input <typeparamref name="T"/> array instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// char[] text = "Hello, world!".ToCharArray();
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <typeparamref name="T"/> array to tokenize.</typeparam>
/// <param name="array">The source <typeparamref name="T"/> array to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="SpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator)
where T : IEquatable<T>
{
return new SpanTokenizer<T>(array, separator);
}
/// <summary>
/// Gets a content hash from the input <typeparamref name="T"/> array instance using the Djb2 algorithm.
/// </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>The Djb2 value for the input <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
{
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(array);
}
}
}

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

@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ArrayPool{T}"/> type.
/// </summary>
public static class ArrayPoolExtensions
{
/// <summary>
/// Changes the number of elements of a rented one-dimensional array to the specified new size.
/// </summary>
/// <typeparam name="T">The type of items into the target array to resize.</typeparam>
/// <param name="pool">The target <see cref="ArrayPool{T}"/> instance to use to resize the array.</param>
/// <param name="array">The rented <typeparamref name="T"/> array to resize, or <see langword="null"/> to create a new array.</param>
/// <param name="newSize">The size of the new array.</param>
/// <param name="clearArray">Indicates whether the contents of the array should be cleared before reuse.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="newSize"/> is less than 0.</exception>
/// <remarks>When this method returns, the caller must not use any references to the old array anymore.</remarks>
public static void Resize<T>(this ArrayPool<T> pool, ref T[]? array, int newSize, bool clearArray = false)
{
// If the old array is null, just create a new one with the requested size
if (array is null)
{
array = pool.Rent(newSize);
return;
}
// If the new size is the same as the current size, do nothing
if (array.Length == newSize)
{
return;
}
/* Rent a new array with the specified size, and copy as many items from the current array
* as possible to the new array. This mirrors the behavior of the Array.Resize API from
* the BCL: if the new size is greater than the length of the current array, copy all the
* items from the original array into the new one. Otherwise, copy as many items as possible,
* until the new array is completely filled, and ignore the remaining items in the first array. */
T[] newArray = pool.Rent(newSize);
int itemsToCopy = Math.Min(array.Length, newSize);
array.AsSpan(0, itemsToCopy).CopyTo(newArray);
pool.Return(array, clearArray);
array = newArray;
}
}
}

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

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="bool"/> type.
/// </summary>
public static class BoolExtensions
{
/// <summary>
/// Converts the given <see cref="bool"/> value into an <see cref="int"/>.
/// </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 int ToInt(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);
}
}
}

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

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Helpers;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="HashCode"/> type.
/// </summary>
public static class HashCodeExtensions
{
/// <summary>
/// Adds a sequence of <typeparamref name="T"/> values to the hash code.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="hashCode">The input <see cref="HashCode"/> instance.</param>
/// <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 NETSTANDARD2_1
where T : notnull
#else
// Same type constraints as HashCode<T>, see comments there
where T : unmanaged
#endif
{
int hash = HashCode<T>.CombineValues(span);
hashCode.Add(hash);
}
}
}

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

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Streams;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="IMemoryOwner{T}"/> type.
/// </summary>
public static class IMemoryOwnerExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memory"/>.</returns>
/// <remarks>
/// The caller does not need to track the lifetime of the input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/>
/// instance, as the returned <see cref="Stream"/> will take care of disposing that buffer when it is closed.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this IMemoryOwner<byte> memory)
{
return new IMemoryOwnerStream(memory);
}
}
}

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

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="Memory{T}"/> type.
/// </summary>
public static class MemoryExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="memory">The input <see cref="Memory{T}"/> of <see cref="byte"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memory"/>.</returns>
/// <remarks>
/// Since this method only receives a <see cref="Memory{T}"/> instance, which does not track
/// the lifetime of its underlying buffer, it is responsability of the caller to manage that.
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this Memory<byte> memory)
{
return new MemoryStream(memory);
}
}
}

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

@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with <see cref="object"/> instances.
/// </summary>
public static class ObjectExtensions
{
/// <summary>
/// Tries to get a boxed <typeparamref name="T"/> value from an input <see cref="object"/> instance.
/// </summary>
/// <typeparam name="T">The type of value to try to unbox.</typeparam>
/// <param name="obj">The input <see cref="object"/> instance to check.</param>
/// <param name="value">The resulting <typeparamref name="T"/> value, if <paramref name="obj"/> was in fact a boxed <typeparamref name="T"/> value.</param>
/// <returns><see langword="true"/> if a <typeparamref name="T"/> value was retrieved correctly, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This extension behaves just like the following method:
/// <code>
/// public static bool TryUnbox&lt;T>(this object obj, out T value)
/// {
/// if (obj is T)
/// {
/// value = (T)obj;
///
/// return true;
/// }
///
/// value = default;
///
/// return false;
/// }
/// </code>
/// But in a more efficient way, and with the ability to also assign the unboxed value
/// directly on an existing T variable, which is not possible with the code above.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryUnbox<T>(this object obj, out T value)
where T : struct
{
if (obj.GetType() == typeof(T))
{
value = Unsafe.Unbox<T>(obj);
return true;
}
value = default;
return false;
}
/// <summary>
/// Unboxes a <typeparamref name="T"/> value from an input <see cref="object"/> instance.
/// </summary>
/// <typeparam name="T">The type of value to unbox.</typeparam>
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
/// <returns>The <typeparamref name="T"/> value boxed in <paramref name="obj"/>.</returns>
/// <remarks>
/// This method doesn't check the actual type of <paramref name="obj"/>, so it is responsability of the caller
/// to ensure it actually represents a boxed <typeparamref name="T"/> value and not some other instance.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousUnbox<T>(this object obj)
where T : struct
{
return ref Unsafe.Unbox<T>(obj);
}
}
}

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

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ReadOnlyMemory{T}"/> type.
/// </summary>
public static class ReadOnlyMemoryExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memory"/>.</returns>
/// <remarks>
/// Since this method only receives a <see cref="Memory{T}"/> instance, which does not track
/// the lifetime of its underlying buffer, it is responsability of the caller to manage that.
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
{
return new MemoryStream(memory);
}
}
}

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

@ -0,0 +1,252 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
/// </summary>
public static partial class ReadOnlySpanExtensions
{
/// <summary>
/// Counts the number of occurrences of a given value into a target <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 to read.</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="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
public static int Count<T>(this ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
// Special vectorized version when using a supported type
if (typeof(T) == typeof(byte) ||
typeof(T) == typeof(sbyte) ||
typeof(T) == typeof(bool))
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
int length = span.Length;
sbyte target = Unsafe.As<T, sbyte>(ref value);
return Count(ref r1, length, target, sbyte.MaxValue);
}
if (typeof(T) == typeof(char) ||
typeof(T) == typeof(ushort) ||
typeof(T) == typeof(short))
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref short r1 = ref Unsafe.As<T, short>(ref r0);
int length = span.Length;
short target = Unsafe.As<T, short>(ref value);
return Count(ref r1, length, target, short.MaxValue);
}
if (typeof(T) == typeof(int) ||
typeof(T) == typeof(uint))
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref int r1 = ref Unsafe.As<T, int>(ref r0);
int length = span.Length;
int target = Unsafe.As<T, int>(ref value);
return Count(ref r1, length, target, int.MaxValue);
}
if (typeof(T) == typeof(long) ||
typeof(T) == typeof(ulong))
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref long r1 = ref Unsafe.As<T, long>(ref r0);
int length = span.Length;
long target = Unsafe.As<T, long>(ref value);
return Count(ref r1, length, target, int.MaxValue);
}
return Count(ref MemoryMarshal.GetReference(span), span.Length, value);
}
/// <summary>
/// Counts the number of occurrences of a given value into a target search space.
/// </summary>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the search space.</param>
/// <param name="length">The number of items in the search space.</param>
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
/// <typeparam name="T">The type of value to look for.</typeparam>
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Count<T>(ref T r0, int length, T value)
where T : IEquatable<T>
{
int
i = 0,
result = 0,
end8 = length - 8;
// Main loop with 8 unrolled iterations
for (; i <= end8; i += 8)
{
result += Unsafe.Add(ref r0, i + 0).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 1).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 2).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 3).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 4).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 5).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 6).Equals(value).ToInt();
result += Unsafe.Add(ref r0, i + 7).Equals(value).ToInt();
}
// Iterate over the remaining values and count those that match
for (; i < length; i++)
{
result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
}
return result;
}
/// <summary>
/// Counts the number of occurrences of a given value into a target search space.
/// </summary>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the search space.</param>
/// <param name="length">The number of items in the search space.</param>
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
/// <param name="max">The maximum amount that a <typeparamref name="T"/> value can reach.</param>
/// <typeparam name="T">The type of value to look for.</typeparam>
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Count<T>(ref T r0, int length, T value, int max)
where T : unmanaged, IEquatable<T>
{
int i = 0, result = 0;
// Only run the SIMD-enabled branch if the Vector<T> APIs are hardware accelerated
if (Vector.IsHardwareAccelerated)
{
int end = length - Vector<T>.Count;
var partials = Vector<T>.Zero;
var vc = new Vector<T>(value);
/* Run the fast path if the input span is short enough.
* There are two branches here because if the search space is large
* enough, the partial results could overflow if the target value
* is present too many times. This upper limit depends on the type
* being used, as the smaller the type is, the shorter the supported
* fast range will be. In the worst case scenario, the same value appears
* always at the offset aligned with the same SIMD value in the current
* register. Therefore, if the input span is longer than that minimum
* threshold, additional checks need to be performed to avoid overflows.
* This value is equal to the maximum (signed) numerical value for the current
* type, divided by the number of value that can fit in a register, minus 1.
* This is because the partial results are accumulated with a dot product,
* which sums them horizontally while still working on the original type.
* Dividing the max value by their count ensures that overflows can't happen.
* The check is moved outside of the loop to enable a branchless version
* of this method if the input span is guaranteed not to cause overflows.
* Otherwise, the safe but slower variant is used. */
int threshold = (max / Vector<T>.Count) - 1;
if (length <= threshold)
{
for (; i <= end; i += Vector<T>.Count)
{
ref T ri = ref Unsafe.Add(ref r0, i);
/* Load the current Vector<T> register, and then use
* Vector.Equals to check for matches. This API sets the
* values corresponding to matching pairs to all 1s.
* Since the input type is guaranteed to always be signed,
* this means that a value with all 1s represents -1, as
* signed numbers are represented in two's complement.
* So we can subtract this intermediate value to the
* partial results, which effectively sums 1 for each match. */
var vi = Unsafe.As<T, Vector<T>>(ref ri);
var ve = Vector.Equals(vi, vc);
partials -= ve;
}
}
else
{
for (int j = 0; i <= end; i += Vector<T>.Count, j++)
{
ref T ri = ref Unsafe.Add(ref r0, i);
// Same as before
var vi = Unsafe.As<T, Vector<T>>(ref ri);
var ve = Vector.Equals(vi, vc);
partials -= ve;
// Additional checks to avoid overflows
if (j == threshold)
{
j = 0;
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
partials = Vector<T>.Zero;
}
}
}
// Compute the horizontal sum of the partial results
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
}
// Iterate over the remaining values and count those that match
for (; i < length; i++)
{
result += Unsafe.Add(ref r0, i).Equals(value).ToInt();
}
return result;
}
/// <summary>
/// Casts a value of a given type to <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>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CastToInt<T>(T value)
where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
{
return Unsafe.As<T, sbyte>(ref value);
}
if (typeof(T) == typeof(short))
{
return Unsafe.As<T, short>(ref value);
}
if (typeof(T) == typeof(int))
{
return Unsafe.As<T, int>(ref value);
}
if (typeof(T) == typeof(long))
{
return (int)Unsafe.As<T, long>(ref value);
}
throw new ArgumentException($"Invalid input type {typeof(T)}", nameof(value));
}
}
}

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

@ -0,0 +1,188 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
/// </summary>
public static partial class ReadOnlySpanExtensions
{
/// <summary>
/// Returns a reference to the first element 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>
/// <returns>A reference to the first element within <paramref name="span"/>.</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 ReadOnlySpan<T> span)
{
return ref MemoryMarshal.GetReference(span);
}
/// <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, int i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
/// <summary>
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlySpan{T}"/> of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlySpan{T}"/></typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="ReadOnlySpan{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="ReadOnlySpan{T}.Length"/> property of the new <see cref="ReadOnlySpan{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<byte> AsBytes<T>(this ReadOnlySpan<T> span)
where T : unmanaged
{
return MemoryMarshal.AsBytes(span);
}
/// <summary>
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="ReadOnlySpan{T}"/> of type <typeparamref name="TTo"/></returns>
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(this ReadOnlySpan<TFrom> span)
where TFrom : struct
where TTo : struct
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
/// <summary>
/// Enumerates the items in the input <see cref="ReadOnlySpan{T}"/> instance, as pairs of value/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// ReadOnlySpan&lt;string&gt; words = new[] { "Hello", ", ", "world", "!" };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this ReadOnlySpan<T> span)
{
return new ReadOnlySpanEnumerable<T>(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="ReadOnlySpan{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// ReadOnlySpan&lt;char&gt; text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="ReadOnlySpan{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T separator)
where T : IEquatable<T>
{
return new ReadOnlySpanTokenizer<T>(span, separator);
}
/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the Djb2 algorithm.
/// </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>
/// <returns>The Djb2 value for the input <see cref="ReadOnlySpan{T}"/> instance.</returns>
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
int
hash = 5381,
length = span.Length,
i = 0,
end8 = length - 8;
// Main loop with 8 unrolled iterations
for (; i <= end8; i += 8)
{
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 0).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 1).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 2).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 3).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 4).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 5).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 6).GetHashCode());
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i + 7).GetHashCode());
}
// Handle the leftover items
for (; i < length; i++)
{
hash = unchecked((hash << 5) + hash + Unsafe.Add(ref r0, i).GetHashCode());
}
return hash;
}
}
}

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

@ -0,0 +1,176 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="Span{T}"/> type.
/// </summary>
public static class SpanExtensions
{
/// <summary>
/// Returns a reference to the first element 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>
/// <returns>A reference to the first element within <paramref name="span"/>.</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 Span<T> span)
{
return ref MemoryMarshal.GetReference(span);
}
/// <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, int i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
/// <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.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="Span{T}"/></typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Span{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="Span{T}.Length"/> property of the new <see cref="Span{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<byte> AsBytes<T>(this Span<T> span)
where T : unmanaged
{
return MemoryMarshal.AsBytes(span);
}
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="Span{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="Span{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="Span{T}"/> of type <typeparamref name="TTo"/></returns>
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<TTo> Cast<TFrom, TTo>(this Span<TFrom> span)
where TFrom : struct
where TTo : struct
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
/// <summary>
/// Counts the number of occurrences of a given value into a target <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 to read.</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="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(this Span<T> span, T value)
where T : IEquatable<T>
{
return ReadOnlySpanExtensions.Count(span, value);
}
/// <summary>
/// Enumerates the items in the input <see cref="Span{T}"/> instance, as pairs of reference/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// Span&lt;int&gt; numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in numbers.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this Span<T> span)
{
return new SpanEnumerable<T>(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="Span{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// Span&lt;char&gt; text = "Hello, world!".ToCharArray();
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="Span{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
where T : IEquatable<T>
{
return new SpanTokenizer<T>(span, separator);
}
/// <summary>
/// Gets a content hash from the input <see cref="Span{T}"/> instance using the Djb2 algorithm.
/// </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>
/// <returns>The Djb2 value for the input <see cref="Span{T}"/> 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 Span<T> span)
where T : notnull
{
return ReadOnlySpanExtensions.GetDjb2HashCode<T>(span);
}
}
}

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

@ -0,0 +1,191 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="SpinLock"/> type.
/// </summary>
public static class SpinLockExtensions
{
/// <summary>
/// Enters a specified <see cref="SpinLock"/> instance and returns a wrapper to use to release the lock.
/// This extension should be used though a <see langword="using"/> block or statement:
/// <code>
/// SpinLock spinLock = new SpinLock();
///
/// using (SpinLockExtensions.Enter(&amp;spinLock))
/// {
/// // Thread-safe code here...
/// }
/// </code>
/// The compiler will take care of releasing the SpinLock when the code goes out of that <see langword="using"/> scope.
/// </summary>
/// <param name="spinLock">A pointer to the target <see cref="SpinLock"/> to use</param>
/// <returns>A wrapper type that will release <paramref name="spinLock"/> when its <see cref="System.IDisposable.Dispose"/> method is called.</returns>
/// <remarks>The returned <see cref="UnsafeLock"/> value shouldn't be used directly: use this extension in a <see langword="using"/> block or statement.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe UnsafeLock Enter(SpinLock* spinLock)
{
return new UnsafeLock(spinLock);
}
/// <summary>
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
/// </summary>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public unsafe ref struct UnsafeLock
{
/// <summary>
/// The <see cref="SpinLock"/>* pointer to the target <see cref="SpinLock"/> value to use.
/// </summary>
private readonly SpinLock* spinLock;
/// <summary>
/// A value indicating whether or not the lock is taken by this <see cref="Lock"/> instance.
/// </summary>
private readonly bool lockTaken;
/// <summary>
/// Initializes a new instance of the <see cref="UnsafeLock"/> struct.
/// </summary>
/// <param name="spinLock">The target <see cref="SpinLock"/> to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UnsafeLock(SpinLock* spinLock)
{
this.spinLock = spinLock;
this.lockTaken = false;
spinLock->Enter(ref this.lockTaken);
}
/// <summary>
/// Implements the duck-typed <see cref="System.IDisposable.Dispose"/> method and releases the current <see cref="SpinLock"/> instance.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (this.lockTaken)
{
this.spinLock->Exit();
}
}
}
#if NETSTANDARD2_1
/// <summary>
/// Enters a specified <see cref="SpinLock"/> instance and returns a wrapper to use to release the lock.
/// This extension should be used though a <see langword="using"/> block or statement:
/// <code>
/// SpinLock spinLock = new SpinLock();
///
/// using (spinLock.Enter())
/// {
/// // Thread-safe code here...
/// }
/// </code>
/// The compiler will take care of releasing the SpinLock when the code goes out of that <see langword="using"/> scope.
/// </summary>
/// <param name="spinLock">The target <see cref="SpinLock"/> to use</param>
/// <returns>A wrapper type that will release <paramref name="spinLock"/> when its <see cref="System.IDisposable.Dispose"/> method is called.</returns>
/// <remarks>The returned <see cref="Lock"/> value shouldn't be used directly: use this extension in a <see langword="using"/> block or statement.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Lock Enter(ref this SpinLock spinLock)
{
return new Lock(ref spinLock);
}
#else
/// <summary>
/// Enters a specified <see cref="SpinLock"/> instance and returns a wrapper to use to release the lock.
/// This extension should be used though a <see langword="using"/> block or statement:
/// <code>
/// private SpinLock spinLock = new SpinLock();
///
/// public void Foo()
/// {
/// using (SpinLockExtensions.Enter(this, ref spinLock))
/// {
/// // Thread-safe code here...
/// }
/// }
/// </code>
/// The compiler will take care of releasing the SpinLock when the code goes out of that <see langword="using"/> scope.
/// </summary>
/// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
/// <param name="spinLock">The target <see cref="SpinLock"/> to use (it must be within <paramref name="owner"/>).</param>
/// <returns>A wrapper type that will release <paramref name="spinLock"/> when its <see cref="System.IDisposable.Dispose"/> method is called.</returns>
/// <remarks>The returned <see cref="Lock"/> value shouldn't be used directly: use this extension in a <see langword="using"/> block or statement.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Lock Enter(object owner, ref SpinLock spinLock)
{
return new Lock(owner, ref spinLock);
}
#endif
/// <summary>
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
/// </summary>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Lock
{
/// <summary>
/// The <see cref="Ref{T}"/> instance pointing to the target <see cref="SpinLock"/> value to use.
/// </summary>
private readonly Ref<SpinLock> spinLock;
/// <summary>
/// A value indicating whether or not the lock is taken by this <see cref="Lock"/> instance.
/// </summary>
private readonly bool lockTaken;
#if NETSTANDARD2_1
/// <summary>
/// Initializes a new instance of the <see cref="Lock"/> struct.
/// </summary>
/// <param name="spinLock">The target <see cref="SpinLock"/> to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Lock(ref SpinLock spinLock)
{
this.spinLock = new Ref<SpinLock>(ref spinLock);
this.lockTaken = false;
spinLock.Enter(ref this.lockTaken);
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Lock"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
/// <param name="spinLock">The target <see cref="SpinLock"/> to use (it must be within <paramref name="owner"/>).</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Lock(object owner, ref SpinLock spinLock)
{
this.spinLock = new Ref<SpinLock>(owner, ref spinLock);
this.lockTaken = false;
spinLock.Enter(ref this.lockTaken);
}
#endif
/// <summary>
/// Implements the duck-typed <see cref="System.IDisposable.Dispose"/> method and releases the current <see cref="SpinLock"/> instance.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (this.lockTaken)
{
this.spinLock.Value.Exit();
}
}
}
}
}

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

@ -0,0 +1,144 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="string"/> type.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Returns a reference to the first element within a given <see cref="string"/>, with no bounds checks.
/// </summary>
/// <param name="text">The input <see cref="string"/> instance.</param>
/// <returns>A reference to the first element within <paramref name="text"/>, or the location it would have used, if <paramref name="text"/> 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 char DangerousGetReference(this string text)
{
var stringData = Unsafe.As<RawStringData>(text);
return ref stringData.Data;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <see cref="string"/>, with no bounds checks.
/// </summary>
/// <param name="text">The input <see cref="string"/> instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="text"/>.</param>
/// <returns>A reference to the element within <paramref name="text"/> 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 char DangerousGetReferenceAt(this string text, int i)
{
var stringData = Unsafe.As<RawStringData>(text);
ref var ri = ref Unsafe.Add(ref stringData.Data, i);
return ref ri;
}
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
// CLR strings are laid out in memory as follows:
// [ sync block || pMethodTable || length || string data .. ]
// ^ ^
// | \-- ref Unsafe.As<RawStringData>(text).Data
// \-- string
// The reference to RawStringData.Data points to the first character in the
// string, skipping over the sync block, method table and string length.
[StructLayout(LayoutKind.Explicit)]
private sealed class RawStringData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
[FieldOffset(4)]
public char Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
/// <summary>
/// Counts the number of occurrences of a given character into a target <see cref="string"/> instance.
/// </summary>
/// <param name="text">The input <see cref="string"/> instance to read.</param>
/// <param name="c">The character to look for.</param>
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count(this string text, char c)
{
return text.AsSpan().Count(c);
}
/// <summary>
/// Enumerates the items in the input <see cref="string"/> instance, as pairs of value/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// string text = "Hello, world!";
///
/// foreach (var item in text.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <param name="text">The source <see cref="string"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="text"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<char> Enumerate(this string text)
{
return new ReadOnlySpanEnumerable<char>(text.AsSpan());
}
/// <summary>
/// Tokenizes the values in the input <see cref="string"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// string text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <param name="text">The source <see cref="string"/> to tokenize.</param>
/// <param name="separator">The separator character to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="text"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer<char> Tokenize(this string text, char separator)
{
return new ReadOnlySpanTokenizer<char>(text.AsSpan(), separator);
}
/// <summary>
/// Gets a content hash from the input <see cref="string"/> instance using the Djb2 algorithm.
/// </summary>
/// <param name="text">The source <see cref="string"/> to enumerate.</param>
/// <returns>The Djb2 value for the input <see cref="string"/> instance.</returns>
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode(this string text)
{
return text.AsSpan().GetDjb2HashCode();
}
}
}

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

@ -0,0 +1,170 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to perform bit operations on numeric types.
/// </summary>
public static class BitHelper
{
/// <summary>
/// Checks whether or not a given bit is set.
/// </summary>
/// <param name="value">The input <see cref="uint"/> value.</param>
/// <param name="n">The position of the bit to check.</param>
/// <returns>Whether or not the n-th bit is set.</returns>
/// <remarks>
/// This method doesn't validate <paramref name="n"/> against the valid range.
/// If the parameter is not valid, the result will just be inconsistent.
/// Additionally, no conditional branches are used to retrieve the flag.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasFlag(uint value, int n)
{
// Read the n-th bit, downcast to byte
byte flag = (byte)((value >> n) & 1);
/* Reinterpret the byte to avoid the test, setnz and
* movzx instructions (asm x64). This is because the JIT
* compiler is able to optimize this reinterpret-cast as
* a single "and eax, 0x1" instruction, whereas if we had
* compared the previous computed flag against 0, the assembly
* would have had to perform the test, set the non-zero
* flag and then extend the (byte) result to eax. */
return Unsafe.As<byte, bool>(ref flag);
}
/// <summary>
/// Sets a bit to a specified value.
/// </summary>
/// <param name="value">The target <see cref="uint"/> value.</param>
/// <param name="n">The position of the bit to set or clear.</param>
/// <param name="flag">The value to assign to the target bit.</param>
/// <remarks>
/// Just like <see cref="HasFlag(uint,int)"/>, this method doesn't validate <paramref name="n"/>
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetFlag(ref uint value, int n, bool flag)
{
value = SetFlag(value, n, flag);
}
/// <summary>
/// Sets a bit to a specified value.
/// </summary>
/// <param name="value">The input <see cref="uint"/> value.</param>
/// <param name="n">The position of the bit to set or clear.</param>
/// <param name="flag">The value to assign to the target bit.</param>
/// <returns>An <see cref="uint"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns>
/// <remarks>
/// Just like <see cref="HasFlag(uint,int)"/>, this method doesn't validate <paramref name="n"/>
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint SetFlag(uint value, int n, bool flag)
{
/* Shift a bit left to the n-th position, negate the
* resulting value and perform an AND with the input value.
* This effectively clears the n-th bit of our input. */
uint
bit = 1u << n,
not = ~bit,
and = value & not;
/* Reinterpret the flag as 1 or 0, and cast to uint.
* The flag is first copied to a local variable as taking
* the address of an argument is slower than doing the same
* for a local variable. This is because when referencing
* the argument the JIT compiler will emit code to temporarily
* move the argument to the stack, and then copy it back.
* With a temporary variable instead, the JIT will be able to
* optimize the method to only use CPU registers. */
bool localFlag = flag;
uint
flag32 = Unsafe.As<bool, byte>(ref localFlag),
/* Finally, we left shift the uint flag to the right position
* and perform an OR with the resulting value of the previous
* operation. This will always guaranteed to work, thanks to the
* initial code clearing that bit before setting it again. */
shift = flag32 << n,
or = and | shift;
return or;
}
/// <summary>
/// Checks whether or not a given bit is set.
/// </summary>
/// <param name="value">The input <see cref="ulong"/> value.</param>
/// <param name="n">The position of the bit to check.</param>
/// <returns>Whether or not the n-th bit is set.</returns>
/// <remarks>
/// This method doesn't validate <paramref name="n"/> against the valid range.
/// If the parameter is not valid, the result will just be inconsistent.
/// Additionally, no conditional branches are used to retrieve the flag.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasFlag(ulong value, int n)
{
// Same logic as the uint version, see that for more info
byte flag = (byte)((value >> n) & 1);
return Unsafe.As<byte, bool>(ref flag);
}
/// <summary>
/// Sets a bit to a specified value.
/// </summary>
/// <param name="value">The target <see cref="ulong"/> value.</param>
/// <param name="n">The position of the bit to set or clear.</param>
/// <param name="flag">The value to assign to the target bit.</param>
/// <remarks>
/// Just like <see cref="HasFlag(ulong,int)"/>, this method doesn't validate <paramref name="n"/>
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetFlag(ref ulong value, int n, bool flag)
{
value = SetFlag(value, n, flag);
}
/// <summary>
/// Sets a bit to a specified value.
/// </summary>
/// <param name="value">The input <see cref="ulong"/> value.</param>
/// <param name="n">The position of the bit to set or clear.</param>
/// <param name="flag">The value to assign to the target bit.</param>
/// <returns>An <see cref="ulong"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns>
/// <remarks>
/// Just like <see cref="HasFlag(ulong,int)"/>, this method doesn't validate <paramref name="n"/>
/// and does not contain branching instructions, so it's well suited for use in tight loops as well.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong SetFlag(ulong value, int n, bool flag)
{
// As with the method above, reuse the same logic as the uint version
ulong
bit = 1ul << n,
not = ~bit,
and = value & not;
bool localFlag = flag;
ulong
flag64 = Unsafe.As<bool, byte>(ref localFlag),
shift = flag64 << n,
or = and | shift;
return or;
}
}
}

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

@ -0,0 +1,412 @@
// 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.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Combines the hash code of sequences of <typeparamref name="T"/> values into a single hash code.
/// </summary>
/// <typeparam name="T">The type of values to hash.</typeparam>
public struct HashCode<T>
#if NETSTANDARD2_1
where T : notnull
#else
/* .NET Standard 2.0 doesn't have the API to check at runtime whether a
* type satisfies the unmanaged constraint, se we enforce that at compile
* time and only expose the APIs of this class in that case. */
where T : unmanaged
#endif
{
/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the xxHash32 algorithm.
/// </summary>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance</param>
/// <returns>The xxHash32 value for the input <see cref="ReadOnlySpan{T}"/> instance</returns>
/// <remarks>The xxHash32 is only guaranteed to be deterministic within the scope of a single app execution</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Combine(ReadOnlySpan<T> span)
{
int hash = CombineValues(span);
return HashCode.Combine(hash);
}
/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance</param>
/// <returns>The hash code for the input <see cref="ReadOnlySpan{T}"/> instance</returns>
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515", Justification = "Compiler directive instead of whitespace")]
internal static int CombineValues(ReadOnlySpan<T> span)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
#if NETSTANDARD2_1
/* 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 CombineValues(ref r0, span.Length);
}
#endif
// Get the info for the target memory area to process
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
long byteSize = (long)span.Length * Unsafe.SizeOf<T>();
// Fast path if the source memory area is not large enough
if (byteSize < BytesProcessor.MinimumSuggestedSize)
{
return CombineValues(ref r0, span.Length);
}
// Use the fast vectorized overload if the input span can be reinterpreted as a sequence of bytes
return byteSize <= int.MaxValue
? BytesProcessor.CombineBytes(ref rb, unchecked((int)byteSize))
: BytesProcessor.CombineBytes(ref rb, byteSize);
}
/// <summary>
/// Gets a content hash from the input memory area.
/// </summary>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
/// <param name="length">The size of the memory area.</param>
/// <returns>The hash code for the input values.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
private static int CombineValues(ref T r0, int length)
{
int
hash = 0, i = 0,
end8 = length - 8;
// Main loop with 8 unrolled iterations
for (; i <= end8; i += 8)
{
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 0).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 1).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 2).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 3).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 4).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 5).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 6).GetHashCode());
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i + 7).GetHashCode());
}
// Handle the leftover items
for (; i < length; i++)
{
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i).GetHashCode());
}
return hash;
}
}
/// <summary>
/// Combines the hash code of sequences of <see cref="byte"/> values into a single hash code.
/// </summary>
/// <remarks>
/// This type is just a wrapper for the <see cref="CombineBytes(ref byte, int)"/> method,
/// which is not defined within <see cref="HashCode{T}"/> for performance reasons.
/// Because <see cref="HashCode{T}"/> is a generic type, each contained method will be JIT
/// compiled into a different executable, even if it's not directly using the generic type
/// parameters of the declaring type at all. Moving this method into a separate, non-generic
/// type allows the runtime to always reuse a single JIT compilation.
/// </remarks>
internal static class BytesProcessor
{
/// <summary>
/// The minimum suggested size for memory areas to process using the APIs in this class
/// </summary>
public const int MinimumSuggestedSize = 512;
/// <summary>
/// Gets a content hash from a given memory area.
/// </summary>
/// <param name="r0">A <see cref="byte"/> reference to the start of the memory area.</param>
/// <param name="length">The size in bytes of the memory area.</param>
/// <returns>The hash code for the contents of the source memory area.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
public static int CombineBytes(ref byte r0, long length)
{
/* This method takes a long as the length parameter, which is needed in case
* the target area is a large sequence of items with a byte size greater than
* one. To maximize efficiency, the target memory area is divided into
* contiguous blocks as large as possible, and the SIMD implementation
* is executed on all of them one by one. The partial results
* are accumulated with the usual hash function. */
int
hash = 0,
runs = unchecked((int)(length / int.MaxValue)),
trailing = unchecked((int)(length - (runs * (long)int.MaxValue)));
// Process chunks of int.MaxValue consecutive bytes
for (int i = 0; i < runs; i++)
{
int partial = CombineBytes(ref r0, int.MaxValue);
hash = unchecked((hash * 397) ^ partial);
r0 = ref Unsafe.Add(ref r0, int.MaxValue);
}
// Process the leftover elements
int tail = CombineBytes(ref r0, trailing);
hash = unchecked((hash * 397) ^ tail);
return hash;
}
/// <summary>
/// Gets a content hash from a given memory area.
/// </summary>
/// <param name="r0">A <see cref="byte"/> reference to the start of the memory area.</param>
/// <param name="length">The size in bytes of the memory area.</param>
/// <returns>The hash code for the contents of the source memory area.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
public static int CombineBytes(ref byte r0, int length)
{
int hash = 0, i = 0;
// Dedicated SIMD branch, if available
if (Vector.IsHardwareAccelerated)
{
/* Test whether the total number of bytes is at least
* equal to the number that can fit in a single SIMD register.
* If that is not the case, skip the entire SIMD branch, which
* also saves the unnecessary computation of partial hash
* values from the accumulation register, and the loading
* of the prime constant in the secondary SIMD register. */
if (length >= 8 * Vector<int>.Count * sizeof(int))
{
var vh = Vector<int>.Zero;
var v397 = new Vector<int>(397);
/* First upper bound for the vectorized path.
* The first loop has sequences of 8 SIMD operations unrolled,
* so assuming that a SIMD register can hold 8 int values at a time,
* it processes 8 * Vector<int>.Count * sizeof(int), which results in
* 128 bytes on SSE registers, 256 on AVX2 and 512 on AVX512 registers. */
var end256 = length - (8 * Vector<int>.Count * sizeof(int));
for (; i <= end256; i += 8 * Vector<int>.Count * sizeof(int))
{
ref byte ri0 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 0) + i);
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
var vp0 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp0, vi0);
ref byte ri1 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 1) + i);
var vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
var vp1 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp1, vi1);
ref byte ri2 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 2) + i);
var vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
var vp2 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp2, vi2);
ref byte ri3 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 3) + i);
var vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
var vp3 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp3, vi3);
ref byte ri4 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 4) + i);
var vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
var vp4 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp4, vi4);
ref byte ri5 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 5) + i);
var vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
var vp5 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp5, vi5);
ref byte ri6 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 6) + i);
var vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
var vp6 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp6, vi6);
ref byte ri7 = ref Unsafe.Add(ref r0, (Vector<int>.Count * sizeof(int) * 7) + i);
var vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
var vp7 = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp7, vi7);
}
/* Second upper bound for the vectorized path.
* Each iteration processes 16 bytes on SSE, 32 bytes on AVX2
* and 64 on AVX512 registers. When this point is reached,
* it means that there are at most 127 bytes remaining on SSE,
* or 255 on AVX2, or 511 on AVX512 systems.*/
var end32 = length - i - (Vector<int>.Count * sizeof(int));
for (; i <= end32; i += Vector<int>.Count * sizeof(int))
{
ref byte ri = ref Unsafe.Add(ref r0, i);
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
var vp = Vector.Multiply(vh, v397);
vh = Vector.Xor(vp, vi);
}
/* Combine the partial hash values in each position.
* The loop below is automatically unrolled by the JIT. */
for (var j = 0; j < Vector<int>.Count; j++)
{
hash = unchecked((hash * 397) ^ vh[j]);
}
}
/* At this point, regardless of whether or not the previous
* branch was taken, there are at most 15 unprocessed bytes
* on SSE systems, 31 on AVX2 systems and 63 on AVX512 systems. */
}
else
{
// Process groups of 64 bytes at a time
var end64 = length - (8 * sizeof(ulong));
for (; i <= end64; i += 8 * sizeof(ulong))
{
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 0) + i);
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
hash = unchecked((hash * 397) ^ (int)value0 ^ (int)(value0 >> 32));
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 1) + i);
var value1 = Unsafe.ReadUnaligned<ulong>(ref ri1);
hash = unchecked((hash * 397) ^ (int)value1 ^ (int)(value1 >> 32));
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 2) + i);
var value2 = Unsafe.ReadUnaligned<ulong>(ref ri2);
hash = unchecked((hash * 397) ^ (int)value2 ^ (int)(value2 >> 32));
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 3) + i);
var value3 = Unsafe.ReadUnaligned<ulong>(ref ri3);
hash = unchecked((hash * 397) ^ (int)value3 ^ (int)(value3 >> 32));
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 4) + i);
var value4 = Unsafe.ReadUnaligned<ulong>(ref ri4);
hash = unchecked((hash * 397) ^ (int)value4 ^ (int)(value4 >> 32));
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 5) + i);
var value5 = Unsafe.ReadUnaligned<ulong>(ref ri5);
hash = unchecked((hash * 397) ^ (int)value5 ^ (int)(value5 >> 32));
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 6) + i);
var value6 = Unsafe.ReadUnaligned<ulong>(ref ri6);
hash = unchecked((hash * 397) ^ (int)value6 ^ (int)(value6 >> 32));
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(ulong) * 7) + i);
var value7 = Unsafe.ReadUnaligned<ulong>(ref ri7);
hash = unchecked((hash * 397) ^ (int)value7 ^ (int)(value7 >> 32));
}
/* At this point, there are up to 63 bytes left.
* If there are at least 32, unroll that iteration with
* the same procedure as before, but as uint values. */
if (length - i >= 8 * sizeof(uint))
{
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(uint) * 0) + i);
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
hash = unchecked((hash * 397) ^ (int)value0);
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(uint) * 1) + i);
var value1 = Unsafe.ReadUnaligned<uint>(ref ri1);
hash = unchecked((hash * 397) ^ (int)value1);
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(uint) * 2) + i);
var value2 = Unsafe.ReadUnaligned<uint>(ref ri2);
hash = unchecked((hash * 397) ^ (int)value2);
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(uint) * 3) + i);
var value3 = Unsafe.ReadUnaligned<uint>(ref ri3);
hash = unchecked((hash * 397) ^ (int)value3);
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(uint) * 4) + i);
var value4 = Unsafe.ReadUnaligned<uint>(ref ri4);
hash = unchecked((hash * 397) ^ (int)value4);
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(uint) * 5) + i);
var value5 = Unsafe.ReadUnaligned<uint>(ref ri5);
hash = unchecked((hash * 397) ^ (int)value5);
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(uint) * 6) + i);
var value6 = Unsafe.ReadUnaligned<uint>(ref ri6);
hash = unchecked((hash * 397) ^ (int)value6);
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(uint) * 7) + i);
var value7 = Unsafe.ReadUnaligned<uint>(ref ri7);
hash = unchecked((hash * 397) ^ (int)value7);
}
// The non-SIMD path leaves up to 31 unprocessed bytes
}
/* At this point there might be up to 31 bytes left on both AVX2 systems,
* and on systems with no hardware accelerated SIMD registers.
* That number would go up to 63 on AVX512 systems, in which case it is
* still useful to perform this last loop unrolling.
* The only case where this branch is never taken is on SSE systems,
* but since those are not so common anyway the code is left here for simplicity.
* What follows is the same procedure as before, but with ushort values,
* so that if there are at least 16 bytes available, those
* will all be processed in a single unrolled iteration. */
if (length - i >= 8 * sizeof(ushort))
{
ref byte ri0 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 0) + i);
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
hash = unchecked((hash * 397) ^ value0);
ref byte ri1 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 1) + i);
var value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
hash = unchecked((hash * 397) ^ value1);
ref byte ri2 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 2) + i);
var value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
hash = unchecked((hash * 397) ^ value2);
ref byte ri3 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 3) + i);
var value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
hash = unchecked((hash * 397) ^ value3);
ref byte ri4 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 4) + i);
var value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
hash = unchecked((hash * 397) ^ value4);
ref byte ri5 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 5) + i);
var value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
hash = unchecked((hash * 397) ^ value5);
ref byte ri6 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 6) + i);
var value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
hash = unchecked((hash * 397) ^ value6);
ref byte ri7 = ref Unsafe.Add(ref r0, (sizeof(ushort) * 7) + i);
var value7 = Unsafe.ReadUnaligned<ushort>(ref ri7);
hash = unchecked((hash * 397) ^ value7);
}
// Handle the leftover items
for (; i < length; i++)
{
hash = unchecked((hash * 397) ^ Unsafe.Add(ref r0, i));
}
return hash;
}
}
}

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

@ -0,0 +1,250 @@
// 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;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
#if NETSTANDARD2_1
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="range">The iteration range.</param>
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(Range range)
where TAction : struct, IAction
{
For(range, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="range">The iteration range.</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>
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(Range range, int minimumActionsPerThread)
where TAction : struct, IAction
{
For(range, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="range">The iteration range.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(Range range, TAction action)
where TAction : struct, IAction
{
For(range, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="range">The iteration range.</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>
/// <remarks>None of the bounds of <paramref name="range"/> can start from an end.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(Range range, TAction action, int minimumActionsPerThread)
where TAction : struct, IAction
{
if (range.Start.IsFromEnd || range.End.IsFromEnd)
{
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(range));
}
int
start = range.Start.Value,
end = range.End.Value;
For(start, end, action, minimumActionsPerThread);
}
#endif
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="start">The starting iteration index.</param>
/// <param name="end">The final iteration index (exclusive).</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(int start, int end)
where TAction : struct, IAction
{
For(start, end, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="start">The starting iteration index.</param>
/// <param name="end">The final iteration index (exclusive).</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 For<TAction>(int start, int end, int minimumActionsPerThread)
where TAction : struct, IAction
{
For(start, end, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="start">The starting iteration index.</param>
/// <param name="end">The final iteration index (exclusive).</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For<TAction>(int start, int end, in TAction action)
where TAction : struct, IAction
{
For(start, end, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction"/>) to invoke for each iteration index.</typeparam>
/// <param name="start">The starting iteration index.</param>
/// <param name="end">The final iteration index (exclusive).</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 For<TAction>(int start, int end, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (start > end)
{
ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd();
}
if (start == end)
{
return;
}
int
count = Math.Abs(start - end),
maxBatches = 1 + ((count - 1) / minimumActionsPerThread),
cores = Environment.ProcessorCount,
numBatches = Math.Min(maxBatches, cores);
// Skip the parallel invocation when a single batch is needed
if (numBatches == 1)
{
for (int i = start; i < end; i++)
{
Unsafe.AsRef(action).Invoke(i);
}
return;
}
int batchSize = 1 + ((count - 1) / numBatches);
var actionInvoker = new ActionInvoker<TAction>(start, end, batchSize, action);
// 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 ActionInvoker<TAction>
where TAction : struct, IAction
{
private readonly int start;
private readonly int end;
private readonly int batchSize;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ActionInvoker(
int start,
int end,
int batchSize,
in TAction action)
{
this.start = start;
this.end = end;
this.batchSize = batchSize;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int
offset = i * this.batchSize,
low = this.start + offset,
high = low + this.batchSize,
stop = Math.Min(high, this.end);
for (int j = low; j < stop; j++)
{
Unsafe.AsRef(this.action).Invoke(j);
}
}
}
}
/// <summary>
/// A contract for actions being executed with an input index.
/// </summary>
/// <remarks>If the <see cref="Invoke"/> method is small enough, it is highly recommended to mark it with <see cref="MethodImplOptions.AggressiveInlining"/>.</remarks>
public interface IAction
{
/// <summary>
/// Executes the action associated with a specific index.
/// </summary>
/// <param name="i">The current index for the action to execute.</param>
void Invoke(int i);
}
}

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

@ -0,0 +1,348 @@
// 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.Drawing;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
#if NETSTANDARD2_1
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="i">The <see cref="Range"/> value indicating the iteration range for the outer loop.</param>
/// <param name="j">The <see cref="Range"/> value indicating the iteration range for the inner loop.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Range i, Range j)
where TAction : struct, IAction2D
{
For2D(i, j, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="i">The <see cref="Range"/> value indicating the iteration range for the outer loop.</param>
/// <param name="j">The <see cref="Range"/> value indicating the iteration range for the inner loop.</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 For2D<TAction>(Range i, Range j, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(i, j, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="i">The <see cref="Range"/> value indicating the iteration range for the outer loop.</param>
/// <param name="j">The <see cref="Range"/> value indicating the iteration range for the inner loop.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Range i, Range j, in TAction action)
where TAction : struct, IAction2D
{
For2D(i, j, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="i">The <see cref="Range"/> value indicating the iteration range for the outer loop.</param>
/// <param name="j">The <see cref="Range"/> value indicating the iteration range for the inner loop.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Range i, Range j, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
if (i.Start.IsFromEnd || i.End.IsFromEnd)
{
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(i));
}
if (j.Start.IsFromEnd || j.End.IsFromEnd)
{
ThrowArgumentExceptionForRangeIndexFromEnd(nameof(j));
}
int
top = i.Start.Value,
bottom = i.End.Value,
left = j.Start.Value,
right = j.End.Value;
For2D(top, bottom, left, right, action, minimumActionsPerThread);
}
#endif
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="area">The <see cref="Rectangle"/> value indicating the 2D iteration area to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Rectangle area)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="area">The <see cref="Rectangle"/> value indicating the 2D iteration area to use.</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 For2D<TAction>(Rectangle area, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="area">The <see cref="Rectangle"/> value indicating the 2D iteration area to use.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Rectangle area, in TAction action)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="area">The <see cref="Rectangle"/> value indicating the 2D iteration area to use.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(Rectangle area, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(area.Top, area.Bottom, area.Left, area.Right, action, minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="top">The starting iteration value for the outer loop.</param>
/// <param name="bottom">The final iteration value for the outer loop (exclusive).</param>
/// <param name="left">The starting iteration value for the inner loop.</param>
/// <param name="right">The final iteration value for the inner loop (exclusive).</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(int top, int bottom, int left, int right)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="top">The starting iteration value for the outer loop.</param>
/// <param name="bottom">The final iteration value for the outer loop (exclusive).</param>
/// <param name="left">The starting iteration value for the inner loop.</param>
/// <param name="right">The final iteration value for the inner loop (exclusive).</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 For2D<TAction>(int top, int bottom, int left, int right, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="top">The starting iteration value for the outer loop.</param>
/// <param name="bottom">The final iteration value for the outer loop (exclusive).</param>
/// <param name="left">The starting iteration value for the inner loop.</param>
/// <param name="right">The final iteration value for the inner loop (exclusive).</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void For2D<TAction>(int top, int bottom, int left, int right, in TAction action)
where TAction : struct, IAction2D
{
For2D(top, bottom, left, right, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop.
/// </summary>
/// <typeparam name="TAction">The type of action (implementing <see cref="IAction2D"/>) to invoke for each pair of iteration indices.</typeparam>
/// <param name="top">The starting iteration value for the outer loop.</param>
/// <param name="bottom">The final iteration value for the outer loop (exclusive).</param>
/// <param name="left">The starting iteration value for the inner loop.</param>
/// <param name="right">The final iteration value for the inner loop (exclusive).</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 For2D<TAction>(int top, int bottom, int left, int right, in TAction action, int minimumActionsPerThread)
where TAction : struct, IAction2D
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (top > bottom)
{
ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom();
}
if (left > right)
{
ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight();
}
// If either side of the target area is empty, no iterations are performed
if (top == bottom || left == right)
{
return;
}
int
height = Math.Abs(top - bottom),
width = Math.Abs(left - right),
count = height * width,
maxBatches = 1 + ((count - 1) / minimumActionsPerThread),
clipBatches = Math.Min(maxBatches, height),
cores = Environment.ProcessorCount,
numBatches = Math.Min(clipBatches, cores);
// Skip the parallel invocation when a single batch is needed
if (numBatches == 1)
{
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
Unsafe.AsRef(action).Invoke(y, x);
}
}
return;
}
int batchHeight = 1 + ((height - 1) / numBatches);
var actionInvoker = new Action2DInvoker<TAction>(top, bottom, left, right, batchHeight, action);
// 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 Action2DInvoker<TAction>
where TAction : struct, IAction2D
{
private readonly int startY;
private readonly int endY;
private readonly int startX;
private readonly int endX;
private readonly int batchHeight;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Action2DInvoker(
int startY,
int endY,
int startX,
int endX,
int batchHeight,
in TAction action)
{
this.startY = startY;
this.endY = endY;
this.startX = startX;
this.endX = endX;
this.batchHeight = batchHeight;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int
heightOffset = i * this.batchHeight,
lowY = this.startY + heightOffset,
highY = lowY + this.batchHeight,
stopY = Math.Min(highY, this.endY);
for (int y = lowY; y < stopY; y++)
{
for (int x = this.startX; x < this.endX; x++)
{
Unsafe.AsRef(this.action).Invoke(y, x);
}
}
}
}
}
/// <summary>
/// A contract for actions being executed with two input indices.
/// </summary>
/// <remarks>If the <see cref="Invoke"/> method is small enough, it is highly recommended to mark it with <see cref="MethodImplOptions.AggressiveInlining"/>.</remarks>
public interface IAction2D
{
/// <summary>
/// Executes the action associated with two specified indices.
/// </summary>
/// <param name="i">The first index for the action to execute.</param>
/// <param name="j">The second index for the action to execute.</param>
void Invoke(int i, int j);
}
}

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

@ -0,0 +1,171 @@
// 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.Runtime.InteropServices;
using System.Threading.Tasks;
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="ReadOnlyMemory{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory<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="ReadOnlyMemory{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>(ReadOnlyMemory<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="ReadOnlyMemory{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>(ReadOnlyMemory<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="ReadOnlyMemory{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>(ReadOnlyMemory<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IInAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
int
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
cores = Environment.ProcessorCount,
numBatches = Math.Min(maxBatches, cores);
// Skip the parallel invocation when a single batch is needed
if (numBatches == 1)
{
foreach (var item in memory.Span)
{
Unsafe.AsRef(action).Invoke(item);
}
return;
}
int batchSize = 1 + ((memory.Length - 1) / numBatches);
var actionInvoker = new InActionInvoker<TItem, TAction>(batchSize, memory, action);
// 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 InActionInvoker<TItem, TAction>
where TAction : struct, IInAction<TItem>
{
private readonly int batchSize;
private readonly ReadOnlyMemory<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public InActionInvoker(
int batchSize,
ReadOnlyMemory<TItem> memory,
in TAction action)
{
this.batchSize = batchSize;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int
low = i * this.batchSize,
high = low + this.batchSize,
end = Math.Min(high, this.memory.Length);
ref TItem r0 = ref MemoryMarshal.GetReference(this.memory.Span);
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, j);
Unsafe.AsRef(this.action).Invoke(rj);
}
}
}
}
/// <summary>
/// A contract for actions being executed on items of a specific type, with readonly access.
/// </summary>
/// <typeparam name="T">The type of items to process.</typeparam>
/// <remarks>If the <see cref="Invoke"/> method is small enough, it is highly recommended to mark it with <see cref="MethodImplOptions.AggressiveInlining"/>.</remarks>
public interface IInAction<T>
{
/// <summary>
/// Executes the action on a specified <typeparamref name="T"/> item.
/// </summary>
/// <param name="item">The current item to process.</param>
void Invoke(in T item);
}
}

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

@ -0,0 +1,171 @@
// 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.Runtime.InteropServices;
using System.Threading.Tasks;
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="Memory{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory<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="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory{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>(Memory<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="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory{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>(Memory<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="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory{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>(Memory<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IRefAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
int
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
cores = Environment.ProcessorCount,
numBatches = Math.Min(maxBatches, cores);
// Skip the parallel invocation when a single batch is needed
if (numBatches == 1)
{
foreach (ref var item in memory.Span)
{
Unsafe.AsRef(action).Invoke(ref item);
}
return;
}
int batchSize = 1 + ((memory.Length - 1) / numBatches);
var actionInvoker = new RefActionInvoker<TItem, TAction>(batchSize, memory, action);
// 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 RefActionInvoker<TItem, TAction>
where TAction : struct, IRefAction<TItem>
{
private readonly int batchSize;
private readonly ReadOnlyMemory<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefActionInvoker(
int batchSize,
ReadOnlyMemory<TItem> memory,
in TAction action)
{
this.batchSize = batchSize;
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int
low = i * this.batchSize,
high = low + this.batchSize,
end = Math.Min(high, this.memory.Length);
ref TItem r0 = ref MemoryMarshal.GetReference(this.memory.Span);
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, j);
Unsafe.AsRef(this.action).Invoke(ref rj);
}
}
}
}
/// <summary>
/// A contract for actions being executed on items of a specific type, with side effect.
/// </summary>
/// <typeparam name="T">The type of items to process.</typeparam>
/// <remarks>If the <see cref="Invoke"/> method is small enough, it is highly recommended to mark it with <see cref="MethodImplOptions.AggressiveInlining"/>.</remarks>
public interface IRefAction<T>
{
/// <summary>
/// Executes the action on a specified <typeparamref name="T"/> item.
/// </summary>
/// <param name="item">The current item to process.</param>
void Invoke(ref T item);
}
}

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

@ -0,0 +1,66 @@
// 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;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid parameter is specified for the minimum actions per thread.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
{
/* Having the argument name here manually typed is
* not ideal, but this way we save passing that string as
* a parameter, since it's always the same anyway.
* Same goes for the other helper methods below. */
throw new ArgumentOutOfRangeException(
"minimumActionsPerThread",
"Each thread needs to perform at least one action");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid start parameter is specified for 1D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd()
{
throw new ArgumentOutOfRangeException("start", "The start parameter must be less than or equal to end");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a range has an index starting from an end.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name)
{
throw new ArgumentException("The bounds of the range can't start from an end", name);
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid top parameter is specified for 2D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom()
{
throw new ArgumentOutOfRangeException("top", "The top parameter must be less than or equal to bottom");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid left parameter is specified for 2D loops.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight()
{
throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right");
}
}
}

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

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Windows Community Toolkit High Performance .NET Standard</Title>
<Description>
This package includes high performance .NET Standard helpers such as:
- ArrayPoolBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt; implementation using pooled arrays, which also supports IMemoryOwner&lt;T&gt;.
- 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.
- String, array, Span&lt;T&gt;, Memory&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.
- Box&lt;T&gt;: a type mapping boxed value types and exposing some utility and high performance methods.
- Ref&lt;T&gt;: a stack-only struct that can store a reference to a value of a specified type.
</Description>
<PackageTags>UWP Toolkit Windows IncrementalLoadingCollection String Array extensions helpers</PackageTags>
<!-- This is a temporary workaround for https://github.com/dotnet/sdk/issues/955 -->
<DebugType>Full</DebugType>
</PropertyGroup>
<!-- .NET Standard 2.0 doesn't have the Span<T> and HashCode types -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<!-- Always import the Unsafe type even on .NET Standard 2.1, so that the 5.0.0 version can be used.
This is necessary to be able to use the Unsafe.Unbox<T>(object) API, which is otherwise missing. -->
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
</ItemGroup>
</Project>

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

@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETSTANDARD2_1
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance
{
/// <summary>
/// A <see langword="struct"/> that can store an optional readonly reference to a value of a specified type.
/// </summary>
/// <typeparam name="T">The type of value to reference.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct NullableReadOnlyRef<T>
{
/// <summary>
/// The 1-length <see cref="ReadOnlySpan{T}"/> instance used to track the target <typeparamref name="T"/> value.
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="NullableReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="value">The readonly reference to the target <typeparamref name="T"/> value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NullableReadOnlyRef(in T value)
{
ref T r0 = ref Unsafe.AsRef(value);
span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
}
/// <summary>
/// Gets a value indicating whether or not the current <see cref="NullableReadOnlyRef{T}"/> instance wraps a valid reference that can be accessed.
/// </summary>
public bool HasValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
// See comment in NullableRef<T> about this
byte length = unchecked((byte)span.Length);
return Unsafe.As<byte, bool>(ref length);
}
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableReadOnlyRef{T}"/> instance.
/// </summary>
/// <exception cref="NullReferenceException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
public ref readonly T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (!HasValue)
{
ThrowNullReferenceException();
}
return ref MemoryMarshal.GetReference(span);
}
}
/// <summary>
/// Throws a <see cref="NullReferenceException"/> when trying to access <see cref="Value"/> for a default instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNullReferenceException()
{
throw new NullReferenceException("The current instance doesn't have a value that can be accessed");
}
}
}
#endif

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

@ -0,0 +1,86 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETSTANDARD2_1
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance
{
/// <summary>
/// A <see langword="struct"/> that can store an optional reference to a value of a specified type.
/// </summary>
/// <typeparam name="T">The type of value to reference.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct NullableRef<T>
{
/// <summary>
/// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
/// </summary>
private readonly Span<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="NullableRef{T}"/> struct.
/// </summary>
/// <param name="value">The reference to the target <typeparamref name="T"/> value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NullableRef(ref T value)
{
span = MemoryMarshal.CreateSpan(ref value, 1);
}
/// <summary>
/// Gets a value indicating whether or not the current <see cref="NullableRef{T}"/> instance wraps a valid reference that can be accessed.
/// </summary>
public bool HasValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
/* We know that the span will always have a length of either
* 1 or 0, se instead of using a cmp instruction and setting the
* zero flag to produce our boolean value, we can just cast
* the length to byte without overflow checks (doing a cast will
* also account for the byte endianness of the current system),
* and then reinterpret that value to a bool flag.
* This results in a single movzx instruction on x86-64. */
byte length = unchecked((byte)span.Length);
return Unsafe.As<byte, bool>(ref length);
}
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableRef{T}"/> instance.
/// </summary>
/// <exception cref="NullReferenceException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
public ref T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (!HasValue)
{
ThrowNullReferenceException();
}
return ref MemoryMarshal.GetReference(span);
}
}
/// <summary>
/// Throws a <see cref="NullReferenceException"/> when trying to access <see cref="Value"/> for a default instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNullReferenceException()
{
throw new NullReferenceException("The current instance doesn't have a value that can be accessed");
}
}
}
#endif

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

@ -0,0 +1,137 @@
// 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;
#if NETSTANDARD2_1
using System.Runtime.InteropServices;
#endif
namespace Microsoft.Toolkit.HighPerformance
{
/// <summary>
/// A <see langword="struct"/> that can store a readonly reference to a value of a specified type.
/// </summary>
/// <typeparam name="T">The type of value to reference.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct ReadOnlyRef<T>
{
#if NETSTANDARD2_1
/// <summary>
/// The 1-length <see cref="ReadOnlySpan{T}"/> instance used to track the target <typeparamref name="T"/> value.
/// </summary>
private readonly ReadOnlySpan<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="value">The readonly reference to the target <typeparamref name="T"/> value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRef(in T value)
{
ref T r0 = ref Unsafe.AsRef(value);
span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
}
/// <summary>
/// Gets the readonly <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>
public ref readonly T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref MemoryMarshal.GetReference(span);
}
/// <summary>
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="ReadOnlyRef{T}"/> one.
/// </summary>
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRef<T>(Ref<T> reference)
{
return new ReadOnlyRef<T>(reference.Value);
}
#else
/// <summary>
/// The owner <see cref="object"/> the current instance belongs to
/// </summary>
private readonly object owner;
/// <summary>
/// The target offset within <see cref="owner"/> the current instance is pointing to
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
/// <param name="offset">The target offset within <paramref name="owner"/> for the target reference.</param>
/// <remarks>The <paramref name="offset"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyRef(object owner, IntPtr offset)
{
this.owner = owner;
this.offset = offset;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="owner"/>).</param>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRef(object owner, in T value)
{
this.owner = owner;
ref T valueRef = ref Unsafe.AsRef(value);
var data = Unsafe.As<RawObjectData>(owner);
ref byte r0 = ref data.Data;
ref byte r1 = ref Unsafe.As<T, byte>(ref valueRef);
offset = Unsafe.ByteOffset(ref r0, ref r1);
}
/// <summary>
/// Gets the readonly <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>
public ref readonly T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var data = Unsafe.As<RawObjectData>(owner);
ref byte r0 = ref data.Data;
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
return ref Unsafe.As<byte, T>(ref r1);
}
}
/// <summary>
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="ReadOnlyRef{T}"/> one.
/// </summary>
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRef<T>(Ref<T> reference)
{
return new ReadOnlyRef<T>(reference.Owner, reference.Offset);
}
#endif
/// <summary>
/// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="ReadOnlyRef{T}"/> instance.
/// </summary>
/// <param name="reference">The input <see cref="ReadOnlyRef{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(ReadOnlyRef<T> reference)
{
return reference.Value;
}
}
}

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

@ -0,0 +1,123 @@
// 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 System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance
{
/// <summary>
/// A <see langword="struct"/> that can store a reference to a value of a specified type.
/// </summary>
/// <typeparam name="T">The type of value to reference.</typeparam>
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct Ref<T>
{
#if NETSTANDARD2_1
/// <summary>
/// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
/// </summary>
private readonly Span<T> span;
/// <summary>
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
/// </summary>
/// <param name="value">The reference to the target <typeparamref name="T"/> value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Ref(ref T value)
{
span = MemoryMarshal.CreateSpan(ref value, 1);
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>
public ref T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref MemoryMarshal.GetReference(span);
}
#else
/// <summary>
/// The owner <see cref="object"/> the current instance belongs to
/// </summary>
internal readonly object Owner;
/// <summary>
/// The target offset within <see cref="Owner"/> the current instance is pointing to
/// </summary>
/// <remarks>
/// Using an <see cref="IntPtr"/> instead of <see cref="int"/> to avoid the int to
/// native int conversion in the generated asm (an extra movsxd on x64).
/// </remarks>
internal readonly IntPtr Offset;
/// <summary>
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="object"/> to create a portable reference for.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="owner"/>).</param>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Ref(object owner, ref T value)
{
this.Owner = owner;
var data = Unsafe.As<RawObjectData>(owner);
ref byte r0 = ref data.Data;
ref byte r1 = ref Unsafe.As<T, byte>(ref value);
Offset = Unsafe.ByteOffset(ref r0, ref r1);
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>
public ref T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var data = Unsafe.As<RawObjectData>(Owner);
ref byte r0 = ref data.Data;
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, Offset);
return ref Unsafe.As<byte, T>(ref r1);
}
}
#endif
/// <summary>
/// Implicitly gets the <typeparamref name="T"/> value from a given <see cref="Ref{T}"/> instance.
/// </summary>
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(Ref<T> reference)
{
return reference.Value;
}
}
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
// CLR objects are laid out in memory as follows:
// [ sync block || pMethodTable || raw data .. ]
// ^ ^
// | \-- ref Unsafe.As<RawObjectData>(owner).Data
// \-- object
// The reference to RawObjectData.Data points to the first data byte in the
// target object, skipping over the sync block, method table and string length.
// This type is not nested to avoid creating multiple generic types.
[StructLayout(LayoutKind.Explicit)]
internal sealed class RawObjectData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
[FieldOffset(0)]
public byte Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
}

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

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers;
using System.IO;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping an <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
/// </summary>
internal sealed class IMemoryOwnerStream : MemoryStream
{
/// <summary>
/// The <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance currently in use.
/// </summary>
private readonly IMemoryOwner<byte> memory;
/// <summary>
/// Initializes a new instance of the <see cref="IMemoryOwnerStream"/> class.
/// </summary>
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance to use.</param>
public IMemoryOwnerStream(IMemoryOwner<byte> memory)
: base(memory.Memory)
{
this.memory = memory;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.memory.Dispose();
}
}
}

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

@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NETSTANDARD2_1
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
internal partial class MemoryStream
{
/// <inheritdoc/>
public override void CopyTo(Stream destination, int bufferSize)
{
ValidateDisposed();
Span<byte> source = this.memory.Span.Slice(this.position);
this.position += source.Length;
destination.Write(source);
}
/// <inheritdoc/>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
}
try
{
int result = Read(buffer.Span);
return new ValueTask<int>(result);
}
catch (OperationCanceledException e)
{
return new ValueTask<int>(Task.FromCanceled<int>(e.CancellationToken));
}
catch (Exception e)
{
return new ValueTask<int>(Task.FromException<int>(e));
}
}
/// <inheritdoc/>
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask(Task.FromCanceled(cancellationToken));
}
try
{
Write(buffer.Span);
return default;
}
catch (OperationCanceledException e)
{
return new ValueTask(Task.FromCanceled(e.CancellationToken));
}
catch (Exception e)
{
return new ValueTask(Task.FromException(e));
}
}
/// <inheritdoc/>
public override int Read(Span<byte> buffer)
{
ValidateDisposed();
int
bytesAvailable = this.memory.Length - this.position,
bytesCopied = Math.Min(bytesAvailable, buffer.Length);
Span<byte> source = this.memory.Span.Slice(this.position, bytesCopied);
source.CopyTo(buffer);
this.position += bytesCopied;
return bytesCopied;
}
/// <inheritdoc/>
public override void Write(ReadOnlySpan<byte> buffer)
{
ValidateDisposed();
ValidateCanWrite();
Span<byte> destination = this.memory.Span.Slice(this.position);
if (!buffer.TryCopyTo(destination))
{
ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.position += buffer.Length;
}
}
}
#endif

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

@ -0,0 +1,110 @@
// 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.IO;
using System.Runtime.CompilerServices;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
internal partial class MemoryStream
{
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when setting the <see cref="Stream.Position"/> property.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForPosition()
{
throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
}
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when an input buffer is <see langword="null"/>.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentNullExceptionForBuffer()
{
throw new ArgumentNullException("buffer", "The buffer is null.");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForOffset()
{
throw new ArgumentOutOfRangeException("offset", "Offset can't be negative.");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentOutOfRangeExceptionForCount()
{
throw new ArgumentOutOfRangeException("count", "Count can't be negative.");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the sum of offset and count exceeds the length of the target buffer.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForLength()
{
throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer");
}
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to write on a readonly stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNotSupportedExceptionForCanWrite()
{
throw new NotSupportedException("The current stream doesn't support writing.");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when trying to write too many bytes to the target stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
{
throw new InvalidOperationException("The current stream can't contain the requested input data.");
}
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedExceptionForSetLength()
{
throw new NotSupportedException("Setting the length is not supported for this stream.");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1615", Justification = "Throw method")]
public static long ThrowArgumentExceptionForSeekOrigin()
{
throw new ArgumentException("The input seek mode is not valid.", "origin");
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
}
}
}

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

@ -0,0 +1,85 @@
// 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.IO;
using System.Runtime.CompilerServices;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="System.Memory{T}"/> or <see cref="System.ReadOnlyMemory{T}"/> instance.
/// </summary>
internal partial class MemoryStream
{
/// <summary>
/// Validates the <see cref="Stream.Position"/> argument.
/// </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)]
private static void ValidatePosition(long position, int length)
{
if ((ulong)position >= (ulong)length)
{
ThrowArgumentOutOfRangeExceptionForPosition();
}
}
/// <summary>
/// Validates the <see cref="Stream.Read(byte[],int,int)"/> or <see cref="Stream.Write(byte[],int,int)"/> arguments.
/// </summary>
/// <param name="buffer">The target array.</param>
/// <param name="offset">The offset within <paramref name="buffer"/>.</param>
/// <param name="count">The number of elements to process within <paramref name="buffer"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateBuffer(byte[]? buffer, int offset, int count)
{
if (buffer is null)
{
ThrowArgumentNullExceptionForBuffer();
}
if (offset < 0)
{
ThrowArgumentOutOfRangeExceptionForOffset();
}
if (count < 0)
{
ThrowArgumentOutOfRangeExceptionForCount();
}
if (offset + count > buffer!.Length)
{
ThrowArgumentExceptionForLength();
}
}
/// <summary>
/// Validates the <see cref="CanWrite"/> property.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ValidateCanWrite()
{
if (!CanWrite)
{
ThrowNotSupportedExceptionForCanWrite();
}
}
/// <summary>
/// Validates that the current instance hasn't been disposed.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ValidateDisposed()
{
if (this.disposed)
{
ThrowObjectDisposedException();
}
}
}
}

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

@ -0,0 +1,317 @@
// 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
#nullable enable
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
/// <remarks>
/// This type is not marked as <see langword="sealed"/> so that it can be inherited by
/// <see cref="IMemoryOwnerStream"/>, which adds the <see cref="IDisposable"/> support for
/// the wrapped buffer. We're not worried about the performance penalty here caused by the JIT
/// not being able to resolve the <see langword="callvirt"/> instruction, as this type is
/// only exposed as a <see cref="Stream"/> anyway, so the generated code would be the same.
/// </remarks>
internal partial class MemoryStream : Stream
{
/// <summary>
/// Indicates whether <see cref="memory"/> was actually a <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
private readonly bool isReadOnly;
/// <summary>
/// The <see cref="Memory{T}"/> instance currently in use.
/// </summary>
private Memory<byte> memory;
/// <summary>
/// The current position within <see cref="memory"/>.
/// </summary>
private int position;
/// <summary>
/// Indicates whether or not the current instance has been disposed
/// </summary>
private bool disposed;
/// <summary>
/// Initializes a new instance of the <see cref="MemoryStream"/> class.
/// </summary>
/// <param name="memory">The input <see cref="Memory{T}"/> instance to use.</param>
public MemoryStream(Memory<byte> memory)
{
this.memory = memory;
this.position = 0;
this.isReadOnly = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryStream"/> class.
/// </summary>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance to use.</param>
public MemoryStream(ReadOnlyMemory<byte> memory)
{
this.memory = MemoryMarshal.AsMemory(memory);
this.position = 0;
this.isReadOnly = true;
}
/// <inheritdoc/>
public override bool CanRead
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !this.disposed;
}
/// <inheritdoc/>
public override bool CanSeek
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !this.disposed;
}
/// <inheritdoc/>
public override bool CanWrite
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !this.isReadOnly && !this.disposed;
}
/// <inheritdoc/>
public override long Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ValidateDisposed();
return this.memory.Length;
}
}
/// <inheritdoc/>
public override long Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ValidateDisposed();
return this.position;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
ValidateDisposed();
ValidatePosition(value, this.memory.Length);
this.position = unchecked((int)value);
}
}
/// <inheritdoc/>
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
try
{
CopyTo(destination, bufferSize);
return Task.CompletedTask;
}
catch (OperationCanceledException e)
{
return Task.FromCanceled(e.CancellationToken);
}
catch (Exception e)
{
return Task.FromException(e);
}
}
/// <inheritdoc/>
public override void Flush()
{
}
/// <inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
try
{
int result = Read(buffer, offset, count);
return Task.FromResult(result);
}
catch (OperationCanceledException e)
{
return Task.FromCanceled<int>(e.CancellationToken);
}
catch (Exception e)
{
return Task.FromException<int>(e);
}
}
/// <inheritdoc/>
public override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
try
{
Write(buffer, offset, count);
return Task.CompletedTask;
}
catch (OperationCanceledException e)
{
return Task.FromCanceled(e.CancellationToken);
}
catch (Exception e)
{
return Task.FromException(e);
}
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
ValidateDisposed();
long index = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => this.position + offset,
SeekOrigin.End => this.memory.Length + offset,
_ => ThrowArgumentExceptionForSeekOrigin()
};
ValidatePosition(index, this.memory.Length);
this.position = unchecked((int)index);
return index;
}
/// <inheritdoc/>
public override void SetLength(long value)
{
ThrowNotSupportedExceptionForSetLength();
}
/// <inheritdoc/>
public override int Read(byte[]? buffer, int offset, int count)
{
ValidateDisposed();
ValidateBuffer(buffer, offset, count);
int
bytesAvailable = this.memory.Length - this.position,
bytesCopied = Math.Min(bytesAvailable, count);
Span<byte>
source = this.memory.Span.Slice(this.position, bytesCopied),
destination = buffer.AsSpan(offset, bytesCopied);
source.CopyTo(destination);
this.position += bytesCopied;
return bytesCopied;
}
/// <inheritdoc/>
public override int ReadByte()
{
ValidateDisposed();
if (this.position == this.memory.Length)
{
return -1;
}
return this.memory.Span[this.position++];
}
/// <inheritdoc/>
public override void Write(byte[]? buffer, int offset, int count)
{
ValidateDisposed();
ValidateCanWrite();
ValidateBuffer(buffer, offset, count);
Span<byte>
source = buffer.AsSpan(offset, count),
destination = this.memory.Span.Slice(this.position);
if (!source.TryCopyTo(destination))
{
ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.position += source.Length;
}
/// <inheritdoc/>
public override void WriteByte(byte value)
{
ValidateDisposed();
ValidateCanWrite();
if (this.position == this.memory.Length)
{
ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.memory.Span[this.position++] = value;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.disposed)
{
return;
}
this.disposed = true;
this.memory = default;
}
}
}

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

@ -8,7 +8,6 @@
<UseUwpMetaPackage>true</UseUwpMetaPackage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />

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

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<!-- .NET Core 2.1 doesn't have the Unsafe type -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Microsoft.Toolkit.HighPerformance\Microsoft.Toolkit.HighPerformance.csproj" />
</ItemGroup>
<Import Project="..\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems" Label="Shared" />
</Project>

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

@ -0,0 +1,121 @@
// 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.IO;
using System.Linq;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Buffers
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_ArrayPoolBufferWriterOfT
{
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
public void Test_ArrayPoolBufferWriterOfT_AllocateAndGetMemoryAndSpan()
{
var writer = new ArrayPoolBufferWriter<byte>();
Assert.AreEqual(writer.Capacity, 256);
Assert.AreEqual(writer.FreeCapacity, 256);
Assert.AreEqual(writer.WrittenCount, 0);
Assert.IsTrue(writer.WrittenMemory.IsEmpty);
Assert.IsTrue(writer.WrittenSpan.IsEmpty);
Span<byte> span = writer.GetSpan(43);
Assert.IsTrue(span.Length >= 43);
writer.Advance(43);
Assert.AreEqual(writer.Capacity, 256);
Assert.AreEqual(writer.FreeCapacity, 256 - 43);
Assert.AreEqual(writer.WrittenCount, 43);
Assert.AreEqual(writer.WrittenMemory.Length, 43);
Assert.AreEqual(writer.WrittenSpan.Length, 43);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.Advance(-1));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.GetMemory(-1));
Assert.ThrowsException<ArgumentException>(() => writer.Advance(1024));
writer.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => writer.WrittenMemory);
Assert.ThrowsException<ObjectDisposedException>(() => writer.WrittenSpan.Length);
Assert.ThrowsException<ObjectDisposedException>(() => writer.Capacity);
Assert.ThrowsException<ObjectDisposedException>(() => writer.FreeCapacity);
Assert.ThrowsException<ObjectDisposedException>(() => writer.Clear());
Assert.ThrowsException<ObjectDisposedException>(() => writer.Advance(1));
}
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
public void Test_ArrayPoolBufferWriterOfT_Clear()
{
using var writer = new ArrayPoolBufferWriter<byte>();
Span<byte> span = writer.GetSpan(4).Slice(0, 4);
byte[] data = { 1, 2, 3, 4 };
data.CopyTo(span);
writer.Advance(4);
Assert.AreEqual(writer.WrittenCount, 4);
Assert.IsTrue(span.SequenceEqual(data));
writer.Clear();
Assert.AreEqual(writer.WrittenCount, 0);
Assert.IsTrue(span.ToArray().All(b => b == 0));
}
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
public void Test_ArrayPoolBufferWriterOfT_MultipleDispose()
{
var writer = new ArrayPoolBufferWriter<byte>();
writer.Dispose();
writer.Dispose();
writer.Dispose();
writer.Dispose();
}
[TestCategory("ArrayPoolBufferWriterOfT")]
[TestMethod]
public void Test_ArrayPoolBufferWriterOfT_AsStream()
{
var writer = new ArrayPoolBufferWriter<byte>();
Span<byte> data = Guid.NewGuid().ToByteArray();
data.CopyTo(writer.GetSpan(data.Length));
writer.Advance(data.Length);
Assert.AreEqual(writer.WrittenCount, data.Length);
Stream stream = writer.AsStream();
Assert.AreEqual(stream.Length, data.Length);
byte[] result = new byte[16];
stream.Read(result, 0, result.Length);
Assert.IsTrue(data.SequenceEqual(result));
stream.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => writer.Capacity);
}
}
}

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

@ -0,0 +1,89 @@
// 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.Linq;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Buffers
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_MemoryOwnerOfT
{
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
public void Test_MemoryOwnerOfT_AllocateAndGetMemoryAndSpan()
{
using var buffer = MemoryOwner<int>.Allocate(127);
Assert.IsTrue(buffer.Length == 127);
Assert.IsTrue(buffer.Memory.Length == 127);
Assert.IsTrue(buffer.Span.Length == 127);
buffer.Span.Fill(42);
Assert.IsTrue(buffer.Memory.Span.ToArray().All(i => i == 42));
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
}
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
[ExpectedException(typeof(ObjectDisposedException))]
public void Test_MemoryOwnerOfT_DisposedMemory()
{
var buffer = MemoryOwner<int>.Allocate(127);
buffer.Dispose();
_ = buffer.Memory;
}
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
[ExpectedException(typeof(ObjectDisposedException))]
public void Test_MemoryOwnerOfT_DisposedSpan()
{
var buffer = MemoryOwner<int>.Allocate(127);
buffer.Dispose();
_ = buffer.Span;
}
[TestCategory("MemoryOwnerOfT")]
[TestMethod]
public void Test_MemoryOwnerOfT_MultipleDispose()
{
var buffer = MemoryOwner<int>.Allocate(127);
buffer.Dispose();
buffer.Dispose();
buffer.Dispose();
buffer.Dispose();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_MemoryOwnerOfT_PooledBuffersAndClear()
{
using (var buffer = MemoryOwner<int>.Allocate(127))
{
buffer.Span.Fill(42);
}
using (var buffer = MemoryOwner<int>.Allocate(127))
{
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
}
using (var buffer = MemoryOwner<int>.Allocate(127, AllocationMode.Clear))
{
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 0));
}
}
}
}

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

@ -0,0 +1,50 @@
// 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.CodeAnalysis;
using System.Linq;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Buffers
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_SpanOwnerOfT
{
[TestCategory("SpanOwnerOfT")]
[TestMethod]
public void Test_SpanOwnerOfT_AllocateAndGetMemoryAndSpan()
{
using var buffer = SpanOwner<int>.Allocate(127);
Assert.IsTrue(buffer.Length == 127);
Assert.IsTrue(buffer.Span.Length == 127);
buffer.Span.Fill(42);
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_SpanOwnerOfT_PooledBuffersAndClear()
{
using (var buffer = SpanOwner<int>.Allocate(127))
{
buffer.Span.Fill(42);
}
using (var buffer = SpanOwner<int>.Allocate(127))
{
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
}
using (var buffer = SpanOwner<int>.Allocate(127, AllocationMode.Clear))
{
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 0));
}
}
}
}

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

@ -0,0 +1,315 @@
// 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.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
public partial class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReference_Int()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
ref int r0 = ref array.DangerousGetReference();
ref int r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReference_String()
{
string[,] array =
{
{ "a", "bb", "ccc" },
{ "dddd", "eeeee", "ffffff" }
};
ref string r0 = ref array.DangerousGetReference();
ref string r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Zero()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
ref int r0 = ref array.DangerousGetReferenceAt(0, 0);
ref int r1 = ref array[0, 0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_DangerousGetReferenceAt_Index()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
ref int r0 = ref array.DangerousGetReferenceAt(1, 3);
ref int r1 = ref array[1, 3];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayMid()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 1, 1, 3, 2);
var expected = new[,]
{
{ false, false, false, false, false },
{ false, true, true, true, false },
{ false, true, true, true, false },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayTwice()
{
bool[,] test = new bool[4, 5];
test.Fill(true, 0, 0, 1, 2);
test.Fill(true, 1, 3, 2, 2);
var expected = new[,]
{
{ true, false, false, false, false },
{ true, false, false, true, true },
{ false, false, false, true, true },
{ false, false, false, false, false },
};
CollectionAssert.AreEqual(expected, test);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_FillArrayNegativeSize()
{
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);
var expected = new[,]
{
{ false, false, false, false, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
{ false, false, true, true, false },
};
CollectionAssert.AreEqual(expected, test);
}
[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()
{
bool[,] test = new bool[5, 4];
test.Fill(true, 3, 2, 3, 3);
var expected = new[,]
{
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false },
{ false, false, true, true },
{ false, false, true, true },
};
CollectionAssert.AreEqual(expected, test);
}
[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 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
int j = 0;
foreach (ref int value in array.GetRow(1))
{
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[1, j++]));
}
CollectionAssert.AreEqual(array.GetRow(1).ToArray(), new[] { 5, 6, 7, 8 });
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetRow(-1)) { }
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in 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 =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
int i = 0;
foreach (ref int value in array.GetColumn(1))
{
Assert.IsTrue(Unsafe.AreSame(ref value, ref array[i++, 1]));
}
CollectionAssert.AreEqual(array.GetColumn(1).ToArray(), new[] { 2, 6, 10 });
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetColumn(-1)) { }
});
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
foreach (var _ in array.GetColumn(20)) { }
});
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_GetColumn_Empty()
{
int[,] array = new int[0, 0];
Assert.ThrowsException<ArgumentOutOfRangeException>(() => array.GetColumn(0).ToArray());
}
#if NETCOREAPP3_0
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan_Empty()
{
int[,] array = new int[0, 0];
Span<int> span = array.AsSpan();
Assert.AreEqual(span.Length, array.Length);
Assert.IsTrue(span.IsEmpty);
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_2D_AsSpan_Populated()
{
int[,] array =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
};
Span<int> span = array.AsSpan();
Assert.AreEqual(span.Length, array.Length);
ref int r0 = ref array[0, 0];
ref int r1 = ref span[0];
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
#endif
}
}

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

@ -0,0 +1,50 @@
// 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
{
[TestClass]
public partial class Test_ArrayExtensions
{
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReference()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');
ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
ref string r1 = ref Unsafe.AsRef(tokens[0]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReferenceAt_Zero()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');
ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReference());
ref string r1 = ref Unsafe.AsRef(tokens.DangerousGetReferenceAt(0));
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ArrayExtensions")]
[TestMethod]
public void Test_ArrayExtensions_DangerousGetReferenceAt_Index()
{
string[] tokens = "aa,bb,cc,dd,ee,ff,gg,hh,ii".Split(',');
ref string r0 = ref Unsafe.AsRef(tokens.DangerousGetReferenceAt(5));
ref string r1 = ref Unsafe.AsRef(tokens[5]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
}
}

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

@ -0,0 +1,97 @@
// 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.Linq;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_ArrayPoolExtensions
{
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Test_ArrayExtensions_InvalidSize()
{
int[] array = null;
ArrayPool<int>.Shared.Resize(ref array, -1);
}
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_NewArray()
{
int[] array = null;
ArrayPool<int>.Shared.Resize(ref array, 10);
Assert.IsNotNull(array);
Assert.IsTrue(array.Length >= 10);
}
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_SameSize()
{
int[] array = ArrayPool<int>.Shared.Rent(10);
int[] backup = array;
ArrayPool<int>.Shared.Resize(ref array, array.Length);
Assert.AreSame(array, backup);
}
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Expand()
{
int[] array = ArrayPool<int>.Shared.Rent(16);
int[] backup = array;
array.AsSpan().Fill(7);
ArrayPool<int>.Shared.Resize(ref array, 32);
Assert.AreNotSame(array, backup);
Assert.IsTrue(array.Length >= 32);
Assert.IsTrue(array.AsSpan(0, 16).ToArray().All(i => i == 7));
}
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Shrink()
{
int[] array = ArrayPool<int>.Shared.Rent(32);
int[] backup = array;
array.AsSpan().Fill(7);
ArrayPool<int>.Shared.Resize(ref array, 16);
Assert.AreNotSame(array, backup);
Assert.IsTrue(array.Length >= 16);
Assert.IsTrue(array.AsSpan(0, 16).ToArray().All(i => i == 7));
}
[TestCategory("ArrayPoolExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Clear()
{
int[] array = ArrayPool<int>.Shared.Rent(16);
int[] backup = array;
array.AsSpan().Fill(7);
ArrayPool<int>.Shared.Resize(ref array, 0, true);
Assert.AreNotSame(array, backup);
Assert.IsTrue(backup.AsSpan(0, 16).ToArray().All(i => i == 0));
}
}
}

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

@ -0,0 +1,30 @@
// 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 Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_BoolExtensions
{
[TestCategory("BoolExtensions")]
[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));
}
[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));
}
}
}

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

@ -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 System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_HashCodeExtensions
{
/// <summary>
/// Gets the list of counts to test the extension for
/// </summary>
private static ReadOnlySpan<int> TestCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, 245_000 };
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount8()
{
TestForType<byte>();
TestForType<sbyte>();
TestForType<bool>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount16()
{
TestForType<ushort>();
TestForType<short>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount32()
{
TestForType<uint>();
TestForType<int>();
TestForType<float>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount64()
{
TestForType<ulong>();
TestForType<long>();
TestForType<double>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat()
{
TestForType<char>();
}
/// <summary>
/// Performs a test for a specified type.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
private static void TestForType<T>()
where T : unmanaged, IEquatable<T>
{
foreach (var count in TestCounts)
{
T[] data = CreateRandomData<T>(count);
HashCode hashCode1 = default;
hashCode1.Add<T>(data);
int hash1 = hashCode1.ToHashCode();
HashCode hashCode2 = default;
hashCode2.Add<T>(data);
int hash2 = hashCode2.ToHashCode();
int hash3 = HashCode<T>.Combine(data);
Assert.AreEqual(hash1, hash2, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash2}");
Assert.AreEqual(hash1, hash3, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash3}");
}
}
/// <summary>
/// Creates a random <typeparamref name="T"/> array filled with random data.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateRandomData<T>(int count)
where T : unmanaged
{
var random = new Random(count);
T[] data = new T[count];
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
return data;
}
}
}

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

@ -0,0 +1,41 @@
// 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.IO;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_IMemoryOwnerExtensions
{
[TestCategory("IMemoryOwnerExtensions")]
[TestMethod]
public void Test_IMemoryOwnerExtensions_EmptyIMemoryOwnerStream()
{
MemoryOwner<byte> buffer = MemoryOwner<byte>.Empty;
Stream stream = buffer.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, buffer.Length);
Assert.IsTrue(stream.CanWrite);
}
[TestCategory("IMemoryOwnerExtensions")]
[TestMethod]
public void Test_MemoryExtensions_IMemoryOwnerStream()
{
MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate(1024);
Stream stream = buffer.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, buffer.Length);
Assert.IsTrue(stream.CanWrite);
}
}
}

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

@ -0,0 +1,41 @@
// 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.IO;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_MemoryExtensions
{
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_EmptyMemoryStream()
{
Memory<byte> memory = default;
Stream stream = memory.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, memory.Length);
Assert.IsTrue(stream.CanWrite);
}
[TestCategory("MemoryExtensions")]
[TestMethod]
public void Test_MemoryExtensions_MemoryStream()
{
Memory<byte> memory = new byte[1024];
Stream stream = memory.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, memory.Length);
Assert.IsTrue(stream.CanWrite);
}
}
}

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

@ -0,0 +1,88 @@
// 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 Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_ObjectExtensions
{
[TestCategory("ObjectExtensions")]
[TestMethod]
public void Test_BoxOfT_PrimitiveTypes()
{
Test(true);
Test(false);
Test<byte>(27);
Test('a');
Test(4221124);
Test(3.14f);
Test(8394324ul);
Test(184013.234324);
}
[TestCategory("ObjectExtensions")]
[TestMethod]
public void Test_BoxOfT_OtherTypes()
{
Test(DateTime.Now);
Test(Guid.NewGuid());
}
internal struct TestStruct : IEquatable<TestStruct>
{
public int Number;
public char Character;
public string Text;
/// <inheritdoc/>
public bool Equals(TestStruct other)
{
return
this.Number == other.Number &&
this.Character == other.Character &&
this.Text == other.Text;
}
}
[TestCategory("ObjectExtensions")]
[TestMethod]
public void TestBoxOfT_CustomStruct()
{
var a = new TestStruct { Number = 42, Character = 'a', Text = "Hello" };
var b = new TestStruct { Number = 38293, Character = 'z', Text = "World" };
Test(a);
Test(b);
}
/// <summary>
/// Tests the extensions type for a given value.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
/// <param name="value">The initial <typeparamref name="T"/> value.</param>
private static void Test<T>(T value)
where T : struct, IEquatable<T>
{
object obj = value;
bool success = obj.TryUnbox(out T result);
Assert.IsTrue(success);
Assert.AreEqual(value, result);
success = obj.TryUnbox(out decimal test);
Assert.IsFalse(success);
Assert.AreEqual(test, default);
result = obj.DangerousUnbox<T>();
Assert.AreEqual(value, result);
}
}
}

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

@ -0,0 +1,41 @@
// 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.IO;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_ReadOnlyMemoryExtensions
{
[TestCategory("ReadOnlyMemoryExtensions")]
[TestMethod]
public void Test_ReadOnlyMemoryExtensions_EmptyMemoryStream()
{
ReadOnlyMemory<byte> memory = default;
Stream stream = memory.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, memory.Length);
Assert.IsFalse(stream.CanWrite);
}
[TestCategory("ReadOnlyMemoryExtensions")]
[TestMethod]
public void Test_ReadOnlyMemoryExtensions_MemoryStream()
{
ReadOnlyMemory<byte> memory = new byte[1024];
Stream stream = memory.AsStream();
Assert.IsNotNull(stream);
Assert.AreEqual(stream.Length, memory.Length);
Assert.IsFalse(stream.CanWrite);
}
}
}

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

@ -0,0 +1,251 @@
// 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.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
public partial class Test_ReadOnlySpanExtensions
{
/// <summary>
/// Gets the list of counts to test the extension for
/// </summary>
private static ReadOnlySpan<int> TestCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, short.MaxValue + 1, 123_938, 1_678_922, 71_890_819 };
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount8()
{
TestForType((byte)123, CreateRandomData);
TestForType((sbyte)123, CreateRandomData);
TestForType(true, CreateRandomData);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCount16()
{
TestForType((ushort)4712, CreateRandomData);
TestForType((short)4712, CreateRandomData);
TestForType((char)4712, CreateRandomData);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1139", Justification = "Easier to tell types apart at a glance")]
public void Test_ReadOnlySpanExtensions_RandomCount32()
{
TestForType((int)37438941, CreateRandomData);
TestForType((uint)37438941, CreateRandomData);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1139", Justification = "Easier to tell types apart at a glance")]
public void Test_ReadOnlySpanExtensions_RandomCount64()
{
TestForType((long)47128480128401, CreateRandomData);
TestForType((ulong)47128480128401, CreateRandomData);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_RandomCountManaged()
{
var value = new Int(37438941);
foreach (var count in TestCounts)
{
var random = new Random(count);
Int[] data = new Int[count];
foreach (ref Int item in data.AsSpan())
{
item = new Int(random.Next());
}
// Fill at least 20% of the items with a matching value
int minimum = count / 20;
for (int i = 0; i < minimum; i++)
{
data[random.Next(0, count)] = value;
}
int result = data.Count(value);
int expected = CountWithForeach(data, value);
Assert.AreEqual(result, expected, $"Failed {typeof(Int)} test with count {count}: got {result} instead of {expected}");
}
}
// Dummy type to test the managed code path of the API
private sealed class Int : IEquatable<Int>
{
private int Value { get; }
public Int(int value) => Value = value;
public bool Equals(Int other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return this.Value == other.Value;
}
public override bool Equals(object obj)
{
return ReferenceEquals(this, obj) || (obj is Int other && Equals(other));
}
public override int GetHashCode()
{
return this.Value;
}
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount8()
{
TestForType((byte)123, (count, _) => CreateFilledData(count, (byte)123));
TestForType((sbyte)123, (count, _) => CreateFilledData(count, (sbyte)123));
TestForType(true, (count, _) => CreateFilledData(count, true));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_FilledCount16()
{
TestForType((ushort)4712, (count, _) => CreateFilledData(count, (ushort)4712));
TestForType((short)4712, (count, _) => CreateFilledData(count, (short)4712));
TestForType((char)4712, (count, _) => CreateFilledData(count, (char)4712));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1139", Justification = "Easier to tell types apart at a glance")]
public void Test_ReadOnlySpanExtensions_FilledCount32()
{
TestForType((int)37438941, (count, _) => CreateFilledData(count, (int)37438941));
TestForType((uint)37438941, (count, _) => CreateFilledData(count, (uint)37438941));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1139", Justification = "Easier to tell types apart at a glance")]
public void Test_ReadOnlySpanExtensions_FilledCount64()
{
TestForType((long)47128480128401, (count, _) => CreateFilledData(count, (long)47128480128401));
TestForType((ulong)47128480128401, (count, _) => CreateFilledData(count, (ulong)47128480128401));
}
/// <summary>
/// Performs a test for a specified type.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
/// <param name="value">The target value to look for.</param>
/// <param name="provider">The function to use to create random data.</param>
private static void TestForType<T>(T value, Func<int, T, T[]> provider)
where T : unmanaged, IEquatable<T>
{
foreach (var count in TestCounts)
{
T[] data = provider(count, value);
int result = data.Count(value);
int expected = CountWithForeach(data, value);
Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
}
}
/// <summary>
/// Counts the number of occurrences of a given value into a target <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to count.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance to read.</param>
/// <param name="value">The value to look for.</param>
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
int count = 0;
foreach (var item in span)
{
if (item.Equals(value))
{
count++;
}
}
return count;
}
/// <summary>
/// Creates a random <typeparamref name="T"/> array filled with random data.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <param name="value">The value to look for.</param>
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateRandomData<T>(int count, T value)
where T : unmanaged
{
var random = new Random(count);
T[] data = new T[count];
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
// Fill at least 20% of the items with a matching value
int minimum = count / 20;
for (int i = 0; i < minimum; i++)
{
data[random.Next(0, count)] = value;
}
return data;
}
/// <summary>
/// Creates a <typeparamref name="T"/> array filled with a given value.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <param name="value">The value to use to populate the array.</param>
/// <returns>An array of <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateFilledData<T>(int count, T value)
where T : unmanaged
{
T[] data = new T[count];
data.AsSpan().Fill(value);
return data;
}
}
}

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

@ -0,0 +1,130 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public partial class Test_ReadOnlySpanExtensions
{
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReference()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
ref int r0 = ref data.DangerousGetReference();
ref int r1 = ref Unsafe.AsRef(data[0]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Zero()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
ref int r0 = ref data.DangerousGetReference();
ref int r1 = ref data.DangerousGetReferenceAt(0);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_DangerousGetReferenceAt_Index()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
ref int r0 = ref data.DangerousGetReferenceAt(5);
ref int r1 = ref Unsafe.AsRef(data[5]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009", Justification = "List<T> of value tuple")]
public void Test_ReadOnlySpanExtensions_Enumerate()
{
ReadOnlySpan<int> data = CreateRandomData<int>(12, default).AsSpan();
List<(int Index, int Value)> values = new List<(int, int)>();
foreach (var item in data.Enumerate())
{
values.Add(item);
}
Assert.AreEqual(values.Count, data.Length);
for (int i = 0; i < data.Length; i++)
{
Assert.AreEqual(data[i], values[i].Value);
Assert.AreEqual(i, values[i].Index);
}
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_Tokenize_Empty()
{
string text = string.Empty;
var result = new List<string>();
foreach (var token in text.AsSpan().Tokenize(','))
{
result.Add(new string(token.ToArray()));
}
var tokens = text.Split(',');
CollectionAssert.AreEqual(result, tokens);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_Tokenize_Csv()
{
string text = "name,surname,city,year,profession,age";
var result = new List<string>();
foreach (var token in text.AsSpan().Tokenize(','))
{
result.Add(new string(token.ToArray()));
}
var tokens = text.Split(',');
CollectionAssert.AreEqual(result, tokens);
}
[TestCategory("ReadOnlySpanExtensions")]
[TestMethod]
public void Test_ReadOnlySpanExtensions_Tokenize_CsvWithMissingValues()
{
string text = ",name,,city,,,profession,,age,,";
var result = new List<string>();
foreach (var token in text.AsSpan().Tokenize(','))
{
result.Add(new string(token.ToArray()));
}
var tokens = text.Split(',');
CollectionAssert.AreEqual(result, tokens);
}
}
}

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

@ -0,0 +1,79 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_SpanExtensions
{
[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReference()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
ref int r0 = ref Unsafe.AsRef(data.DangerousGetReference());
ref int r1 = ref Unsafe.AsRef(data[0]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReferenceAt_Zero()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
ref int r0 = ref Unsafe.AsRef(data.DangerousGetReference());
ref int r1 = ref Unsafe.AsRef(data.DangerousGetReferenceAt(0));
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReferenceAt_Index()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
ref int r0 = ref Unsafe.AsRef(data.DangerousGetReferenceAt(5));
ref int r1 = ref Unsafe.AsRef(data[5]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("SpanExtensions")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009", Justification = "List<T> of value tuple")]
public void Test_SpanExtensions_Enumerate()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };
List<(int Index, int Value)> values = new List<(int, int)>();
foreach (var item in data.Enumerate())
{
values.Add((item.Index, item.Value));
item.Value = item.Index * 10;
}
Assert.AreEqual(values.Count, data.Length);
for (int i = 0; i < data.Length; i++)
{
Assert.AreEqual(data[i], i * 10);
Assert.AreEqual(i, values[i].Index);
Assert.AreEqual(i + 1, values[i].Value);
}
}
}
}

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

@ -0,0 +1,74 @@
// 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.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_SpinLockExtensions
{
[TestCategory("SpinLockExtensions")]
[TestMethod]
public unsafe void Test_ArrayExtensions_Pointer()
{
SpinLock spinLock = default;
SpinLock* p = &spinLock;
int sum = 0;
Parallel.For(0, 1000, i =>
{
for (int j = 0; j < 10; j++)
{
using (SpinLockExtensions.Enter(p))
{
sum++;
}
}
});
Assert.AreEqual(sum, 1000 * 10);
}
[TestCategory("SpinLockExtensions")]
[TestMethod]
public void Test_ArrayExtensions_Ref()
{
var spinLockOwner = new SpinLockOwner();
int sum = 0;
Parallel.For(0, 1000, i =>
{
for (int j = 0; j < 10; j++)
{
#if NETCOREAPP2_1 || WINDOWS_UWP
using (SpinLockExtensions.Enter(spinLockOwner, ref spinLockOwner.Lock))
#else
using (spinLockOwner.Lock.Enter())
#endif
{
sum++;
}
}
});
Assert.AreEqual(sum, 1000 * 10);
}
/// <summary>
/// A dummy model that owns a <see cref="SpinLock"/> object.
/// </summary>
private sealed class SpinLockOwner
{
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Quick ref access for tests")]
public SpinLock Lock;
}
}
}

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

@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_StringExtensions
{
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_DangerousGetReference()
{
string text = "Hello, world!";
ref char r0 = ref text.DangerousGetReference();
ref char r1 = ref Unsafe.AsRef(text.AsSpan()[0]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_DangerousGetReferenceAt_Zero()
{
string text = "Hello, world!";
ref char r0 = ref text.DangerousGetReference();
ref char r1 = ref text.DangerousGetReferenceAt(0);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_DangerousGetReferenceAt_Index()
{
string text = "Hello, world!";
ref char r0 = ref text.DangerousGetReferenceAt(5);
ref char r1 = ref Unsafe.AsRef(text.AsSpan()[5]);
Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}
}
}

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

@ -0,0 +1,67 @@
// 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.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_BitHelper
{
[TestCategory("BitHelper")]
[TestMethod]
public void Test_BitHelper_HasFlag_UInt32()
{
uint value = 0b10_1001110101_0110010010_1100100010;
Assert.IsFalse(BitHelper.HasFlag(value, 0));
Assert.IsTrue(BitHelper.HasFlag(value, 1));
Assert.IsFalse(BitHelper.HasFlag(value, 2));
Assert.IsTrue(BitHelper.HasFlag(value, 9));
Assert.IsFalse(BitHelper.HasFlag(value, 10));
Assert.IsFalse(BitHelper.HasFlag(value, 30));
Assert.IsTrue(BitHelper.HasFlag(value, 31));
}
[TestCategory("BitHelper")]
[TestMethod]
public void Test_BitHelper_SetFlag_UInt32()
{
Assert.AreEqual(0b1u, BitHelper.SetFlag(0u, 0, true));
Assert.AreEqual(4u, BitHelper.SetFlag(4u, 1, false));
Assert.AreEqual(0b110u, BitHelper.SetFlag(2u, 2, true));
Assert.AreEqual(unchecked((uint)int.MinValue), BitHelper.SetFlag(0u, 31, true));
}
[TestCategory("BitHelper")]
[TestMethod]
public void Test_UInt64Extensions_HasFlag()
{
ulong value = 0b10_1001110101_0110010010_1100100010;
value |= 1ul << 63;
Assert.IsFalse(BitHelper.HasFlag(value, 0));
Assert.IsTrue(BitHelper.HasFlag(value, 1));
Assert.IsFalse(BitHelper.HasFlag(value, 2));
Assert.IsTrue(BitHelper.HasFlag(value, 9));
Assert.IsFalse(BitHelper.HasFlag(value, 10));
Assert.IsFalse(BitHelper.HasFlag(value, 30));
Assert.IsTrue(BitHelper.HasFlag(value, 31));
Assert.IsTrue(BitHelper.HasFlag(value, 63));
}
[TestCategory("BitHelper")]
[TestMethod]
public void Test_UInt64Extensions_SetFlag()
{
Assert.AreEqual(0b1ul, BitHelper.SetFlag(0u, 0, true));
Assert.AreEqual(4ul, BitHelper.SetFlag(4u, 1, false));
Assert.AreEqual(0b110ul, BitHelper.SetFlag(2u, 2, true));
Assert.AreEqual(1ul << 31, BitHelper.SetFlag(0ul, 31, true));
Assert.AreEqual(1ul << 63, BitHelper.SetFlag(0ul, 63, true));
}
}
}

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

@ -0,0 +1,129 @@
// 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.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_HashCodeOfT
{
/// <summary>
/// Gets the list of counts to test the extension for
/// </summary>
private static ReadOnlySpan<int> TestCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, short.MaxValue + 1, 123_938, 1_678_922, 71_890_819 };
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount8()
{
TestForType<byte>();
TestForType<sbyte>();
TestForType<bool>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount16()
{
TestForType<ushort>();
TestForType<short>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount32()
{
TestForType<uint>();
TestForType<int>();
TestForType<float>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorSupportedTypes_TestRepeatCount64()
{
TestForType<ulong>();
TestForType<long>();
TestForType<double>();
}
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_VectorUnsupportedTypes_TestRepeat()
{
TestForType<char>();
}
#if NETCOREAPP3_0
[TestCategory("HashCodeOfT")]
[TestMethod]
public void Test_HashCodeOfT_ManagedType_TestRepeat()
{
var random = new Random();
foreach (var count in TestCounts.Slice(0, 8))
{
string[] data = new string[count];
foreach (ref string text in data.AsSpan())
{
text = random.NextDouble().ToString("E");
}
int hash1 = HashCode<string>.Combine(data);
int hash2 = HashCode<string>.Combine(data);
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.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
private static void TestForType<T>()
where T : unmanaged, IEquatable<T>
{
foreach (var count in TestCounts)
{
T[] data = CreateRandomData<T>(count);
int hash1 = HashCode<T>.Combine(data);
int hash2 = HashCode<T>.Combine(data);
Assert.AreEqual(hash1, hash2, $"Failed {typeof(T)} test with count {count}: got {hash1} and then {hash2}");
}
}
/// <summary>
/// Creates a random <typeparamref name="T"/> array filled with random data.
/// </summary>
/// <typeparam name="T">The type of items to put in the array.</typeparam>
/// <param name="count">The number of array items to create.</param>
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
[Pure]
private static T[] CreateRandomData<T>(int count)
where T : unmanaged
{
var random = new Random(count);
T[] data = new T[count];
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
return data;
}
}
}

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

@ -0,0 +1,99 @@
// 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 Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
[TestClass]
public partial class Test_ParallelHelper
{
/// <summary>
/// Gets the list of counts to test the For (1D) extensions for
/// </summary>
private static ReadOnlySpan<int> TestForCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, short.MaxValue + 1, 123_938, 1_678_922, 71_890_819 };
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ForWithIndices()
{
foreach (int count in TestForCounts)
{
int[] data = new int[count];
ParallelHelper.For(0, data.Length, new Assigner(data));
foreach (var item in data.Enumerate())
{
if (item.Index != item.Value)
{
Assert.Fail($"Invalid item at position {item.Index}, value was {item.Value}");
}
}
}
}
#if NETCOREAPP3_0
[TestCategory("ParallelHelper")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_ParallelHelper_ForInvalidRange_FromEnd()
{
ParallelHelper.For<Assigner>(..^1);
}
[TestCategory("ParallelHelper")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_ParallelHelper_ForInvalidRange_RangeAll()
{
ParallelHelper.For<Assigner>(..);
}
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ForWithRanges()
{
foreach (int count in TestForCounts)
{
int[] data = new int[count];
ParallelHelper.For(..data.Length, new Assigner(data));
foreach (var item in data.Enumerate())
{
if (item.Index != item.Value)
{
Assert.Fail($"Invalid item at position {item.Index}, value was {item.Value}");
}
}
}
}
#endif
/// <summary>
/// A type implementing <see cref="IAction"/> to initialize an array
/// </summary>
private readonly struct Assigner : IAction
{
private readonly int[] array;
public Assigner(int[] array) => this.array = array;
/// <inheritdoc/>
public void Invoke(int i)
{
if (this.array[i] != 0)
{
throw new InvalidOperationException($"Invalid target position {i}, was {this.array[i]} instead of 0");
}
this.array[i] = i;
}
}
}
}

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

@ -0,0 +1,113 @@
// 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.Drawing;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
/// <summary>
/// Gets the list of counts to test the For2D extensions for
/// </summary>
private static ReadOnlySpan<Size> TestFor2DSizes => new[]
{
new Size(0, 0),
new Size(0, 1),
new Size(1, 1),
new Size(3, 3),
new Size(1024, 1024),
new Size(512, 2175),
new Size(4039, 11231)
};
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_For2DWithIndices()
{
foreach (var size in TestFor2DSizes)
{
int[,] data = new int[size.Height, size.Width];
ParallelHelper.For2D(0, size.Height, 0, size.Width, new Assigner2D(data));
for (int i = 0; i < size.Height; i++)
{
for (int j = 0; j < size.Width; j++)
{
if (data[i, j] != unchecked(i * 397 ^ j))
{
Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
}
}
}
}
}
#if NETCOREAPP3_0
[TestCategory("ParallelHelper")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_ParallelHelper_For2DInvalidRange_FromEnd()
{
ParallelHelper.For2D<Assigner2D>(..^1, ..4);
}
[TestCategory("ParallelHelper")]
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Test_ParallelHelper_For2DInvalidRange_RangeAll()
{
ParallelHelper.For2D<Assigner2D>(..5, ..);
}
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_For2DWithRanges()
{
foreach (var size in TestFor2DSizes)
{
int[,] data = new int[size.Height, size.Width];
ParallelHelper.For2D(..size.Height, ..size.Width, new Assigner2D(data));
for (int i = 0; i < size.Height; i++)
{
for (int j = 0; j < size.Width; j++)
{
if (data[i, j] != unchecked(i * 397 ^ j))
{
Assert.Fail($"Invalid item at position [{i},{j}], value was {data[i, j]} instead of {unchecked(i * 397 ^ j)}");
}
}
}
}
}
#endif
/// <summary>
/// A type implementing <see cref="IAction"/> to initialize a 2D array
/// </summary>
private readonly struct Assigner2D : IAction2D
{
private readonly int[,] array;
public Assigner2D(int[,] array) => this.array = array;
/// <inheritdoc/>
public void Invoke(int i, int j)
{
if (this.array[i, j] != 0)
{
throw new InvalidOperationException($"Invalid target position [{i},{j}], was {this.array[i, j]} instead of 0");
}
this.array[i, j] = unchecked(i * 397 ^ j);
}
}
}
}

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

@ -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 System.Threading;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
[TestCategory("ParallelHelper")]
[TestMethod]
public unsafe void Test_ParallelHelper_ForEach_In()
{
foreach (int count in TestForCounts)
{
int[] data = CreateRandomData(count);
int sum = 0;
ParallelHelper.ForEach<int, Summer>(data.AsMemory(), new Summer(&sum));
int expected = 0;
foreach (int n in data)
{
expected += n;
}
Assert.AreEqual(sum, expected, $"The sum doesn't match, was {sum} instead of {expected}");
}
}
/// <summary>
/// A type implementing <see cref="IInAction{T}"/> to sum array elements.
/// </summary>
private readonly unsafe struct Summer : IInAction<int>
{
private readonly int* ptr;
public Summer(int* ptr) => this.ptr = ptr;
/// <inheritdoc/>
public void Invoke(in int i) => Interlocked.Add(ref Unsafe.AsRef<int>(this.ptr), i);
}
/// <summary>
/// Creates a random <see cref="int"/> array filled with random numbers.
/// </summary>
/// <param name="count">The number of array items to create.</param>
/// <returns>An array of random <see cref="int"/> elements.</returns>
[Pure]
private static int[] CreateRandomData(int count)
{
var random = new Random(count);
int[] data = new int[count];
foreach (ref int n in data.AsSpan())
{
n = random.Next(0, byte.MaxValue);
}
return data;
}
}
}

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

@ -0,0 +1,52 @@
// 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 Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ForEach_Ref()
{
foreach (int count in TestForCounts)
{
int[] data = CreateRandomData(count);
int[] copy = data.AsSpan().ToArray();
foreach (ref int n in copy.AsSpan())
{
n = unchecked(n * 397);
}
ParallelHelper.ForEach(data.AsMemory(), new Multiplier(397));
for (int i = 0; i < data.Length; i++)
{
if (data[i] != copy[i])
{
Assert.Fail($"Item #{i} was not a match, was {data[i]} instead of {copy[i]}");
}
}
}
}
/// <summary>
/// A type implementing <see cref="IRefAction{T}"/> to multiply array elements.
/// </summary>
private readonly struct Multiplier : IRefAction<int>
{
private readonly int factor;
public Multiplier(int factor) => this.factor = factor;
/// <inheritdoc/>
public void Invoke(ref int i) => i = unchecked(i * this.factor);
}
}
}

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

@ -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.Linq;
using Microsoft.Toolkit.HighPerformance.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException;
namespace UnitTests.HighPerformance.Helpers
{
public partial class Test_ParallelHelper
{
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ParameterName_ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
{
try
{
ParallelHelper.For<DummyAction>(0, 1, -1);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(ParallelHelper).GetMethods()
where
method.Name == nameof(ParallelHelper.For) &&
method.IsGenericMethodDefinition
let typeParams = method.GetGenericArguments()
let normalParams = method.GetParameters()
where
typeParams.Length == 1 &&
normalParams.Length == 3 &&
normalParams.All(p => p.ParameterType == typeof(int))
select normalParams[2].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ParameterName_ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd()
{
try
{
ParallelHelper.For<DummyAction>(1, 0);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(ParallelHelper).GetMethods()
where
method.Name == nameof(ParallelHelper.For) &&
method.IsGenericMethodDefinition
let typeParams = method.GetGenericArguments()
let normalParams = method.GetParameters()
where
typeParams.Length == 1 &&
normalParams.Length == 2 &&
normalParams.All(p => p.ParameterType == typeof(int))
select normalParams[0].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ParameterName_ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom()
{
try
{
ParallelHelper.For2D<DummyAction2D>(1, 0, 0, 1);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(ParallelHelper).GetMethods()
where
method.Name == nameof(ParallelHelper.For2D) &&
method.IsGenericMethodDefinition
let typeParams = method.GetGenericArguments()
let normalParams = method.GetParameters()
where
typeParams.Length == 1 &&
normalParams.Length == 4 &&
normalParams.All(p => p.ParameterType == typeof(int))
select normalParams[0].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("ParallelHelper")]
[TestMethod]
public void Test_ParallelHelper_ParameterName_ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight()
{
try
{
ParallelHelper.For2D<DummyAction2D>(0, 1, 1, 0);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(ParallelHelper).GetMethods()
where
method.Name == nameof(ParallelHelper.For2D) &&
method.IsGenericMethodDefinition
let typeParams = method.GetGenericArguments()
let normalParams = method.GetParameters()
where
typeParams.Length == 1 &&
normalParams.Length == 4 &&
normalParams.All(p => p.ParameterType == typeof(int))
select normalParams[2].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
/// <summary>
/// A dummy type implementing <see cref="IAction"/>
/// </summary>
private readonly struct DummyAction : IAction
{
/// <inheritdoc/>
public void Invoke(int i)
{
}
}
/// <summary>
/// A dummy type implementing <see cref="IAction2D"/>
/// </summary>
private readonly struct DummyAction2D : IAction2D
{
/// <inheritdoc/>
public void Invoke(int i, int j)
{
}
}
}
}

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

@ -0,0 +1,35 @@
// 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.IO;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Streams
{
[TestClass]
public class Test_IMemoryOwnerStream
{
[TestCategory("IMemoryOwnerStream")]
[TestMethod]
public void Test_IMemoryOwnerStream_Lifecycle()
{
MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate(100);
Stream stream = buffer.AsStream();
Assert.IsTrue(stream.CanRead);
Assert.IsTrue(stream.CanSeek);
Assert.IsTrue(stream.CanWrite);
Assert.AreEqual(stream.Length, buffer.Length);
Assert.AreEqual(stream.Position, 0);
stream.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => buffer.Memory);
}
}
}

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

@ -0,0 +1,182 @@
// 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.IO;
using System.Linq;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Streams
{
public partial class Test_MemoryStream
{
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentExceptionForPosition()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Position = -1;
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
Assert.AreEqual(e.ParamName, nameof(Stream.Position));
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentExceptionForSeekOrigin()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Seek(0, (SeekOrigin)int.MinValue);
}
catch (ArgumentException e) when (e.GetType() == typeof(ArgumentException))
{
var method = stream.GetType().GetMethod(nameof(Stream.Seek));
var name = method!.GetParameters()[1].Name;
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentNullExceptionForNullBuffer()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Write(null, 0, 10);
}
catch (ArgumentNullException e) when (e.GetType() == typeof(ArgumentNullException))
{
var name = (
from method in typeof(Stream).GetMethods()
where method.Name == nameof(Stream.Write)
let normalParams = method.GetParameters()
where
normalParams.Length == 3 &&
normalParams[0].ParameterType == typeof(byte[]) &&
normalParams[1].ParameterType == typeof(int) &&
normalParams[2].ParameterType == typeof(int)
select normalParams[0].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentOutOfRangeExceptionForNegativeOffset()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Write(new byte[1], -1, 10);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(Stream).GetMethods()
where method.Name == nameof(Stream.Write)
let normalParams = method.GetParameters()
where
normalParams.Length == 3 &&
normalParams[0].ParameterType == typeof(byte[]) &&
normalParams[1].ParameterType == typeof(int) &&
normalParams[2].ParameterType == typeof(int)
select normalParams[1].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentOutOfRangeExceptionForNegativeCount()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Write(new byte[1], 0, -1);
}
catch (ArgumentOutOfRangeException e) when (e.GetType() == typeof(ArgumentOutOfRangeException))
{
var name = (
from method in typeof(Stream).GetMethods()
where method.Name == nameof(Stream.Write)
let normalParams = method.GetParameters()
where
normalParams.Length == 3 &&
normalParams[0].ParameterType == typeof(byte[]) &&
normalParams[1].ParameterType == typeof(int) &&
normalParams[2].ParameterType == typeof(int)
select normalParams[2].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ParameterName_ThrowArgumentExceptionForExceededBufferSize()
{
var stream = new byte[10].AsMemory().AsStream();
try
{
stream.Write(new byte[1], 0, 10);
}
catch (ArgumentException e) when (e.GetType() == typeof(ArgumentException))
{
var name = (
from method in typeof(Stream).GetMethods()
where method.Name == nameof(Stream.Write)
let normalParams = method.GetParameters()
where
normalParams.Length == 3 &&
normalParams[0].ParameterType == typeof(byte[]) &&
normalParams[1].ParameterType == typeof(int) &&
normalParams[2].ParameterType == typeof(int)
select normalParams[0].Name).Single();
Assert.AreEqual(e.ParamName, name);
return;
}
Assert.Fail("Failed to raise correct exception");
}
}
}

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

@ -0,0 +1,252 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Streams
{
[TestClass]
public partial class Test_MemoryStream
{
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_Lifecycle()
{
Memory<byte> memory = new byte[100];
Stream stream = memory.AsStream();
Assert.IsTrue(stream.CanRead);
Assert.IsTrue(stream.CanSeek);
Assert.IsTrue(stream.CanWrite);
Assert.AreEqual(stream.Length, memory.Length);
Assert.AreEqual(stream.Position, 0);
stream.Dispose();
Assert.IsFalse(stream.CanRead);
Assert.IsFalse(stream.CanSeek);
Assert.IsFalse(stream.CanWrite);
Assert.ThrowsException<ObjectDisposedException>(() => stream.Length);
Assert.ThrowsException<ObjectDisposedException>(() => stream.Position);
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_Seek()
{
Stream stream = new byte[100].AsMemory().AsStream();
Assert.AreEqual(stream.Position, 0);
stream.Position = 42;
Assert.AreEqual(stream.Position, 42);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Position = -1);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Position = 120);
stream.Seek(0, SeekOrigin.Begin);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(-1, SeekOrigin.Begin));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(120, SeekOrigin.Begin));
Assert.AreEqual(stream.Position, 0);
stream.Seek(-1, SeekOrigin.End);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(20, SeekOrigin.End));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(-120, SeekOrigin.End));
Assert.AreEqual(stream.Position, stream.Length - 1);
stream.Seek(42, SeekOrigin.Begin);
stream.Seek(20, SeekOrigin.Current);
stream.Seek(-30, SeekOrigin.Current);
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(-64, SeekOrigin.Current));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Seek(80, SeekOrigin.Current));
Assert.AreEqual(stream.Position, 32);
}
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ReadWrite_Array()
{
Stream stream = new byte[100].AsMemory().AsStream();
byte[] data = CreateRandomData(64);
stream.Write(data, 0, data.Length);
Assert.AreEqual(stream.Position, data.Length);
stream.Position = 0;
byte[] result = new byte[data.Length];
int bytesRead = stream.Read(result, 0, result.Length);
Assert.AreEqual(bytesRead, result.Length);
Assert.AreEqual(stream.Position, data.Length);
Assert.IsTrue(data.AsSpan().SequenceEqual(result));
Assert.ThrowsException<ArgumentNullException>(() => stream.Write(null, 0, 10));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Write(data, -1, 10));
Assert.ThrowsException<ArgumentException>(() => stream.Write(data, 200, 10));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => stream.Write(data, 0, -24));
Assert.ThrowsException<ArgumentException>(() => stream.Write(data, 0, 200));
stream.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => stream.Write(data, 0, data.Length));
}
[TestCategory("MemoryStream")]
[TestMethod]
public async Task Test_MemoryStream_ReadWriteAsync_Array()
{
Stream stream = new byte[100].AsMemory().AsStream();
byte[] data = CreateRandomData(64);
await stream.WriteAsync(data, 0, data.Length);
Assert.AreEqual(stream.Position, data.Length);
stream.Position = 0;
byte[] result = new byte[data.Length];
int bytesRead = await stream.ReadAsync(result, 0, result.Length);
Assert.AreEqual(bytesRead, result.Length);
Assert.AreEqual(stream.Position, data.Length);
Assert.IsTrue(data.AsSpan().SequenceEqual(result));
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => stream.WriteAsync(null, 0, 10));
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(() => stream.WriteAsync(data, -1, 10));
await Assert.ThrowsExceptionAsync<ArgumentException>(() => stream.WriteAsync(data, 200, 10));
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(() => stream.WriteAsync(data, 0, -24));
await Assert.ThrowsExceptionAsync<ArgumentException>(() => stream.WriteAsync(data, 0, 200));
stream.Dispose();
await Assert.ThrowsExceptionAsync<ObjectDisposedException>(() => stream.WriteAsync(data, 0, data.Length));
}
[TestCategory("MemoryStream")]
[TestMethod]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500", Justification = "Array initialization")]
public void Test_MemoryStream_ReadWriteByte()
{
Stream stream = new byte[4].AsMemory().AsStream();
ReadOnlySpan<byte> data = stackalloc byte[] { 1, 128, 255, 32 };
foreach (var item in data.Enumerate())
{
Assert.AreEqual(stream.Position, item.Index);
stream.WriteByte(item.Value);
}
Assert.AreEqual(stream.Position, data.Length);
stream.Position = 0;
Span<byte> result = stackalloc byte[4];
foreach (ref byte value in result)
{
value = checked((byte)stream.ReadByte());
}
Assert.AreEqual(stream.Position, data.Length);
Assert.IsTrue(data.SequenceEqual(result));
Assert.ThrowsException<InvalidOperationException>(() => stream.WriteByte(128));
int exitCode = stream.ReadByte();
Assert.AreEqual(exitCode, -1);
}
#if NETCOREAPP2_1 || NETCOREAPP3_0
[TestCategory("MemoryStream")]
[TestMethod]
public void Test_MemoryStream_ReadWrite_Span()
{
Stream stream = new byte[100].AsMemory().AsStream();
Memory<byte> data = CreateRandomData(64);
stream.Write(data.Span);
Assert.AreEqual(stream.Position, data.Length);
stream.Position = 0;
Span<byte> result = new byte[data.Length];
int bytesRead = stream.Read(result);
Assert.AreEqual(bytesRead, result.Length);
Assert.AreEqual(stream.Position, data.Length);
Assert.IsTrue(data.Span.SequenceEqual(result));
}
[TestCategory("MemoryStream")]
[TestMethod]
public async Task Test_MemoryStream_ReadWriteAsync_Memory()
{
Stream stream = new byte[100].AsMemory().AsStream();
Memory<byte> data = CreateRandomData(64);
await stream.WriteAsync(data);
Assert.AreEqual(stream.Position, data.Length);
stream.Position = 0;
Memory<byte> result = new byte[data.Length];
int bytesRead = await stream.ReadAsync(result);
Assert.AreEqual(bytesRead, result.Length);
Assert.AreEqual(stream.Position, data.Length);
Assert.IsTrue(data.Span.SequenceEqual(result.Span));
}
#endif
/// <summary>
/// Creates a random <see cref="byte"/> array filled with random data.
/// </summary>
/// <param name="count">The number of array items to create.</param>
/// <returns>The returned random array.</returns>
[Pure]
private static byte[] CreateRandomData(int count)
{
var random = new Random(DateTime.Now.Ticks.GetHashCode());
byte[] data = new byte[count];
foreach (ref byte n in MemoryMarshal.AsBytes(data.AsSpan()))
{
n = (byte)random.Next(0, byte.MaxValue);
}
return data;
}
}
}

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

@ -0,0 +1,104 @@
// 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 Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_BoxOfT
{
[TestCategory("BoxOfT")]
[TestMethod]
public void Test_BoxOfT_PrimitiveTypes()
{
Test(true, false);
Test<byte>(27, 254);
Test('a', '$');
Test(4221124, 1241241);
Test(3.14f, 2342.222f);
Test(8394324ul, 1343431241ul);
Test(184013.234324, 14124.23423);
}
[TestCategory("BoxOfT")]
[TestMethod]
public void Test_BoxOfT_OtherTypes()
{
Test(DateTime.Now, DateTime.FromBinary(278091429014));
Test(Guid.NewGuid(), Guid.NewGuid());
}
internal struct TestStruct : IEquatable<TestStruct>
{
public int Number;
public char Character;
public string Text;
/// <inheritdoc/>
public bool Equals(TestStruct other)
{
return
this.Number == other.Number &&
this.Character == other.Character &&
this.Text == other.Text;
}
}
[TestCategory("BoxOfT")]
[TestMethod]
public void TestBoxOfT_CustomStruct()
{
var a = new TestStruct { Number = 42, Character = 'a', Text = "Hello" };
var b = new TestStruct { Number = 38293, Character = 'z', Text = "World" };
Test(a, b);
}
/// <summary>
/// Tests the <see cref="Box{T}"/> type for a given pair of values.
/// </summary>
/// <typeparam name="T">The type to test.</typeparam>
/// <param name="value">The initial <typeparamref name="T"/> value.</param>
/// <param name="test">The new <typeparamref name="T"/> value to assign and test.</param>
private static void Test<T>(T value, T test)
where T : struct, IEquatable<T>
{
Box<T> box = value;
Assert.AreEqual(box.GetReference(), value);
Assert.AreEqual(box.ToString(), value.ToString());
Assert.AreEqual(box.GetHashCode(), value.GetHashCode());
object obj = value;
bool success = Box<T>.TryGetFrom(obj, out box);
Assert.IsTrue(success);
Assert.IsTrue(ReferenceEquals(obj, box));
Assert.IsNotNull(box);
Assert.AreEqual(box.GetReference(), value);
Assert.AreEqual(box.ToString(), value.ToString());
Assert.AreEqual(box.GetHashCode(), value.GetHashCode());
box = Box<T>.DangerousGetFrom(obj);
Assert.IsTrue(ReferenceEquals(obj, box));
Assert.AreEqual(box.GetReference(), value);
Assert.AreEqual(box.ToString(), value.ToString());
Assert.AreEqual(box.GetHashCode(), value.GetHashCode());
box.GetReference() = test;
Assert.AreEqual(box.GetReference(), test);
Assert.AreEqual(box.ToString(), test.ToString());
Assert.AreEqual(box.GetHashCode(), test.GetHashCode());
Assert.AreEqual(obj, test);
}
}
}

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

@ -0,0 +1,51 @@
// 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 NETCOREAPP3_0
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_NullableReadOnlyRefOfT
{
[TestCategory("NullableReadOnlyRefOfT")]
[TestMethod]
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_Ok()
{
int value = 1;
var reference = new NullableReadOnlyRef<int>(value);
Assert.IsTrue(reference.HasValue);
Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value)));
}
[TestCategory("NullableReadOnlyRefOfT")]
[TestMethod]
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_Null()
{
NullableReadOnlyRef<int> reference = default;
Assert.IsFalse(reference.HasValue);
}
[TestCategory("NullableReadOnlyRefOfT")]
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public void Test_NullableReadOnlyRefOfT_CreateNullableReadOnlyRefOfT_Null_Exception()
{
NullableReadOnlyRef<int> reference = default;
_ = reference.Value;
}
}
}
#endif

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

@ -0,0 +1,55 @@
// 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 NETCOREAPP3_0
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_NullableRefOfT
{
[TestCategory("NullableRefOfT")]
[TestMethod]
public void Test_RefOfT_CreateNullableRefOfT_Ok()
{
int value = 1;
var reference = new NullableRef<int>(ref value);
Assert.IsTrue(reference.HasValue);
Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value));
reference.Value++;
Assert.AreEqual(value, 2);
}
[TestCategory("NullableRefOfT")]
[TestMethod]
public void Test_RefOfT_CreateNullableRefOfT_Null()
{
NullableRef<int> reference = default;
Assert.IsFalse(reference.HasValue);
}
[TestCategory("NullableRefOfT")]
[TestMethod]
[ExpectedException(typeof(NullReferenceException))]
public void Test_RefOfT_CreateNullableRefOfT_Null_Exception()
{
NullableRef<int> reference = default;
_ = reference.Value;
}
}
}
#endif

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

@ -0,0 +1,45 @@
// 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.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_ReadOnlyRefOfT
{
[TestCategory("ReadOnlyRefOfT")]
[TestMethod]
#if NETCOREAPP2_1 || WINDOWS_UWP
public void Test_RefOfT_CreateRefOfT()
{
var model = new ReadOnlyFieldOwner();
var reference = new ReadOnlyRef<int>(model, model.Value);
Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(model.Value), ref Unsafe.AsRef(reference.Value)));
}
/// <summary>
/// A dummy model that owns an <see cref="int"/> field.
/// </summary>
private sealed class ReadOnlyFieldOwner
{
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Ref readonly access for tests")]
public readonly int Value = 1;
}
#else
public void Test_RefOfT_CreateRefOfT()
{
int value = 1;
var reference = new ReadOnlyRef<int>(value);
Assert.IsTrue(Unsafe.AreSame(ref value, ref Unsafe.AsRef(reference.Value)));
}
#endif
}
}

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

@ -0,0 +1,53 @@
// 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.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance
{
[TestClass]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Test class for generic type")]
public class Test_RefOfT
{
[TestCategory("RefOfT")]
[TestMethod]
#if NETCOREAPP2_1 || WINDOWS_UWP
public void Test_RefOfT_CreateRefOfT()
{
var model = new FieldOwner { Value = 1 };
var reference = new Ref<int>(model, ref model.Value);
Assert.IsTrue(Unsafe.AreSame(ref model.Value, ref reference.Value));
reference.Value++;
Assert.AreEqual(model.Value, 2);
}
/// <summary>
/// A dummy model that owns an <see cref="int"/> field.
/// </summary>
private sealed class FieldOwner
{
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Quick ref access for tests")]
public int Value;
}
#else
public void Test_RefOfT_CreateRefOfT()
{
int value = 1;
var reference = new Ref<int>(ref value);
Assert.IsTrue(Unsafe.AreSame(ref value, ref reference.Value));
reference.Value++;
Assert.AreEqual(value, 2);
}
#endif
}
}

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

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>9b3a94a6-0d29-4523-880b-6938e2efeef7</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>UnitTests.HighPerformance.Shared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_ArrayPoolBufferWriter{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryOwner{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayPoolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_IMemoryOwnerExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlyMemoryExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_MemoryExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ObjectExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_BoolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_HashCodeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpanExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.Count.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.cs" />
<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.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)Streams\Test_IMemoryOwnerStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.ThrowExceptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Streams\Test_MemoryStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_Box{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_NullableReadOnlyRef{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_NullableRef{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_Ref{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Test_ReadOnlyRef{T}.cs" />
</ItemGroup>
</Project>

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

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>9b3a94a6-0d29-4523-880b-6938e2efeef7</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="UnitTests.HighPerformance.Shared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/StoreLogo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

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

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap mp">
<Identity Name="2db00f4f-0cf1-44bd-97ad-2bf93c49958d"
Publisher="CN=Microsoft"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="2db00f4f-0cf1-44bd-97ad-2bf93c49958d" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>UnitTests.HighPerformance.UWP</DisplayName>
<PublisherDisplayName>Microsoft</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="vstest.executionengine.universal.App"
Executable="$targetnametoken$.exe"
EntryPoint="UnitTests.HighPerformance.UWP.App">
<uap:VisualElements
DisplayName="UnitTests.HighPerformance.UWP"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="UnitTests.HighPerformance.UWP"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClientServer" />
<Capability Name="privateNetworkClientServer" />
</Capabilities>
</Package>

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

@ -0,0 +1,21 @@
// 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.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("UnitTests.HighPerformance.UWP")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("UnitTests.HighPerformance.UWP")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyMetadata("TargetPlatform", "UAP")]
[assembly: ComVisible(false)]

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

@ -0,0 +1,29 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>

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

@ -0,0 +1,6 @@
<Application
x:Class="UnitTests.HighPerformance.UWP.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>

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

@ -0,0 +1,53 @@
// 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 Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace UnitTests.HighPerformance.UWP
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public sealed partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <inheritdoc/>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
// Ensure the current window is active
Window.Current.Activate();
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
}
}
}

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

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{5524523E-DB0F-41F7-A0D4-43128422A342}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnitTests.HighPerformance.UWP</RootNamespace>
<AssemblyName>UnitTests.HighPerformance.UWP</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.17763.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<Target Name="Pack">
</Target>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
<OutputPath>bin\ARM64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<SDKReference Include="TestPlatform.Universal, Version=$(UnitTestPlatformVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>1.4.0</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>1.4.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe">
<Version>4.7.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Microsoft.Toolkit.HighPerformance\Microsoft.Toolkit.HighPerformance.csproj">
<Project>{7e30d48c-4cd8-47be-b557-10a20391dcc4}</Project>
<Name>Microsoft.Toolkit.HighPerformance</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems" Label="Shared" />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

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

@ -90,10 +90,23 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputT
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Media", "Microsoft.Toolkit.Uwp.UI.Media\Microsoft.Toolkit.Uwp.UI.Media.csproj", "{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.HighPerformance", "Microsoft.Toolkit.HighPerformance\Microsoft.Toolkit.HighPerformance.csproj", "{7E30D48C-4CD8-47BE-B557-10A20391DCC4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.HighPerformance.NetCore", "UnitTests\UnitTests.HighPerformance.NetCore\UnitTests.HighPerformance.NetCore.csproj", "{D9BDBC68-3D0A-47FC-9C88-0BF769101644}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UnitTests.HighPerformance.Shared", "UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.shproj", "{9B3A94A6-0D29-4523-880B-6938E2EFEEF7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HighPerformance", "HighPerformance", "{262CDB74-CF21-47AC-8DD9-CBC33C73B7CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.HighPerformance.UWP", "UnitTests\UnitTests.HighPerformance.UWP\UnitTests.HighPerformance.UWP.csproj", "{5524523E-DB0F-41F7-A0D4-43128422A342}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{5524523e-db0f-41f7-a0d4-43128422a342}*SharedItemsImports = 4
UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13
UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{9b3a94a6-0d29-4523-880b-6938e2efeef7}*SharedItemsImports = 13
UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{bab1caf4-c400-4a7f-a987-c576de63cffd}*SharedItemsImports = 4
UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{d9bdbc68-3d0a-47fc-9c88-0bf769101644}*SharedItemsImports = 5
UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{efa96b3c-857e-4659-b942-6bef7719f4ca}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -786,13 +799,9 @@ Global
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Debug|x86.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Debug|x86.Build.0 = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|Any CPU.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|Any CPU.Build.0 = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|ARM.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|ARM.Build.0 = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|ARM64.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|ARM64.Build.0 = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|x64.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|x64.Build.0 = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Native|x86.ActiveCfg = Debug|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|Any CPU.Build.0 = Release|Any CPU
@ -804,6 +813,89 @@ Global
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x64.Build.0 = Release|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.ActiveCfg = Release|Any CPU
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.Build.0 = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|ARM.Build.0 = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|ARM64.Build.0 = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|x64.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|x64.Build.0 = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|x86.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Debug|x86.Build.0 = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Native|Any CPU.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Native|ARM.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Native|ARM64.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Native|x64.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Native|x86.ActiveCfg = Debug|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|Any CPU.Build.0 = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|ARM.ActiveCfg = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|ARM.Build.0 = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|ARM64.ActiveCfg = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|ARM64.Build.0 = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|x64.ActiveCfg = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|x64.Build.0 = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|x86.ActiveCfg = Release|Any CPU
{7E30D48C-4CD8-47BE-B557-10A20391DCC4}.Release|x86.Build.0 = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|ARM.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|ARM.Build.0 = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|ARM64.Build.0 = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|x64.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|x64.Build.0 = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|x86.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Debug|x86.Build.0 = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Native|Any CPU.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Native|ARM.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Native|ARM64.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Native|x64.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Native|x86.ActiveCfg = Debug|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|Any CPU.Build.0 = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|ARM.ActiveCfg = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|ARM.Build.0 = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|ARM64.ActiveCfg = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|ARM64.Build.0 = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|x64.ActiveCfg = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|x64.Build.0 = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|x86.ActiveCfg = Release|Any CPU
{D9BDBC68-3D0A-47FC-9C88-0BF769101644}.Release|x86.Build.0 = Release|Any CPU
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|Any CPU.ActiveCfg = Debug|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|Any CPU.Build.0 = Debug|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM.ActiveCfg = Debug|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM.Build.0 = Debug|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM.Deploy.0 = Debug|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM64.ActiveCfg = Debug|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM64.Build.0 = Debug|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|ARM64.Deploy.0 = Debug|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x64.ActiveCfg = Debug|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x64.Build.0 = Debug|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x64.Deploy.0 = Debug|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x86.ActiveCfg = Debug|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x86.Build.0 = Debug|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Debug|x86.Deploy.0 = Debug|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Native|Any CPU.ActiveCfg = Release|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Native|ARM.ActiveCfg = Release|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Native|ARM64.ActiveCfg = Release|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Native|x64.ActiveCfg = Release|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Native|x86.ActiveCfg = Release|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|Any CPU.ActiveCfg = Release|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|Any CPU.Build.0 = Release|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM.ActiveCfg = Release|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM.Build.0 = Release|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM.Deploy.0 = Release|ARM
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM64.ActiveCfg = Release|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM64.Build.0 = Release|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|ARM64.Deploy.0 = Release|ARM64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x64.ActiveCfg = Release|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x64.Build.0 = Release|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x64.Deploy.0 = Release|x64
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x86.ActiveCfg = Release|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x86.Build.0 = Release|x86
{5524523E-DB0F-41F7-A0D4-43128422A342}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -827,6 +919,10 @@ Global
{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF} = {096ECFD7-7035-4487-9C87-81DCE9389620}
{A122EA02-4DE7-413D-BFBF-AF7DFC668DD6} = {B30036C4-D514-4E5B-A323-587A061772CE}
{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC}
{D9BDBC68-3D0A-47FC-9C88-0BF769101644} = {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF}
{9B3A94A6-0D29-4523-880B-6938E2EFEEF7} = {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF}
{262CDB74-CF21-47AC-8DD9-CBC33C73B7CF} = {B30036C4-D514-4E5B-A323-587A061772CE}
{5524523E-DB0F-41F7-A0D4-43128422A342} = {262CDB74-CF21-47AC-8DD9-CBC33C73B7CF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345}