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>
|
@ -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<byte> buffer = SpanOwner<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<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<string> words = new[] { "Hello", ", ", "world", "!" };
|
||||
///
|
||||
/// foreach (var item in words.Enumerate())
|
||||
/// {
|
||||
/// // Access the index and value of each item here...
|
||||
/// int index = item.Index;
|
||||
/// string value = item.Value;
|
||||
/// }
|
||||
/// </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<char> 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<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="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<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 <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(&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<T>: an IBufferWriter<T> implementation using pooled arrays, which also supports IMemoryOwner<T>.
|
||||
- MemoryOwner<T>: an IMemoryOwner<T> implementation with an embedded length and a fast Span<T> accessor.
|
||||
- SpanOwner<T>: a stack-only type with the ability to rent a buffer of a specified length and getting a Span<T> from it.
|
||||
- String, array, Span<T>, Memory<T> extensions and more, all focused on high performance.
|
||||
- HashCode<T>: 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<T>: a type mapping boxed value types and exposing some utility and high performance methods.
|
||||
- Ref<T>: 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>
|
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/LockScreenLogo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/SplashScreen.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 7.5 KiB |
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/Square150x150Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 2.9 KiB |
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/Square44x44Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 1.6 KiB |
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
Normal file
После Ширина: | Высота: | Размер: 1.2 KiB |
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
UnitTests/UnitTests.HighPerformance.UWP/Assets/Wide310x150Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 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)' < '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}
|
||||
|
|