Коммит
9ae2475c25
|
@ -1,19 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Applied to a method that will never return under any circumstance.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class DoesNotReturnAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,36 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that a given <see cref="ParameterValue"/> also indicates
|
||||
/// whether the method will not return (eg. throw an exception).
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal sealed class DoesNotReturnIfAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DoesNotReturnIfAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parameterValue">
|
||||
/// The condition parameter value. Code after the method will be considered unreachable
|
||||
/// by diagnostics if the argument to the associated parameter matches this value.
|
||||
/// </param>
|
||||
public DoesNotReturnIfAttribute(bool parameterValue)
|
||||
{
|
||||
ParameterValue = parameterValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter value should be <see langword="true"/>.
|
||||
/// </summary>
|
||||
public bool ParameterValue { get; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,20 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that an output will not be <see langword="null"/> even if the corresponding type allows it.
|
||||
/// Specifies that an input argument was not <see langword="null"/> when the call returns.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]
|
||||
internal sealed class NotNullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,28 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NET5_0
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate to the compiler that the <c>.locals init</c> flag should not be set in method headers.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(
|
||||
AttributeTargets.Module |
|
||||
AttributeTargets.Class |
|
||||
AttributeTargets.Struct |
|
||||
AttributeTargets.Interface |
|
||||
AttributeTargets.Constructor |
|
||||
AttributeTargets.Method |
|
||||
AttributeTargets.Property |
|
||||
AttributeTargets.Event,
|
||||
Inherited = false)]
|
||||
internal sealed class SkipLocalsInitAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,225 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
#if NETSTANDARD1_4
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with types.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The mapping of built-in types to their simple representation.
|
||||
/// </summary>
|
||||
private static readonly IReadOnlyDictionary<Type, string> BuiltInTypesMap = new Dictionary<Type, string>
|
||||
{
|
||||
[typeof(bool)] = "bool",
|
||||
[typeof(byte)] = "byte",
|
||||
[typeof(sbyte)] = "sbyte",
|
||||
[typeof(short)] = "short",
|
||||
[typeof(ushort)] = "ushort",
|
||||
[typeof(char)] = "char",
|
||||
[typeof(int)] = "int",
|
||||
[typeof(uint)] = "uint",
|
||||
[typeof(float)] = "float",
|
||||
[typeof(long)] = "long",
|
||||
[typeof(ulong)] = "ulong",
|
||||
[typeof(double)] = "double",
|
||||
[typeof(decimal)] = "decimal",
|
||||
[typeof(object)] = "object",
|
||||
[typeof(string)] = "string",
|
||||
[typeof(void)] = "void"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A thread-safe mapping of precomputed string representation of types.
|
||||
/// </summary>
|
||||
private static readonly ConditionalWeakTable<Type, string> DisplayNames = new ConditionalWeakTable<Type, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a type.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <returns>The string representation of <paramref name="type"/>.</returns>
|
||||
[Pure]
|
||||
public static string ToTypeString(this Type type)
|
||||
{
|
||||
// Local function to create the formatted string for a given type
|
||||
static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan<Type> typeArguments)
|
||||
{
|
||||
// Primitive types use the keyword name
|
||||
if (BuiltInTypesMap.TryGetValue(type, out string? typeName))
|
||||
{
|
||||
return typeName!;
|
||||
}
|
||||
|
||||
// Array types are displayed as Foo[]
|
||||
if (type.IsArray)
|
||||
{
|
||||
var elementType = type.GetElementType()!;
|
||||
var rank = type.GetArrayRank();
|
||||
|
||||
return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]";
|
||||
}
|
||||
|
||||
// By checking generic types here we are only interested in specific cases,
|
||||
// ie. nullable value types or value typles. We have a separate path for custom
|
||||
// generic types, as we can't rely on this API in that case, as it doesn't show
|
||||
// a difference between nested types that are themselves generic, or nested simple
|
||||
// types from a generic declaring type. To deal with that, we need to manually track
|
||||
// the offset within the array of generic arguments for the whole constructed type.
|
||||
if (type.IsGenericType())
|
||||
{
|
||||
var genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
// Nullable<T> types are displayed as T?
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
{
|
||||
var nullableArguments = type.GetGenericArguments();
|
||||
|
||||
return $"{FormatDisplayString(nullableArguments[0], 0, nullableArguments)}?";
|
||||
}
|
||||
|
||||
// ValueTuple<T1, T2> types are displayed as (T1, T2)
|
||||
if (genericTypeDefinition == typeof(ValueTuple<>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
|
||||
{
|
||||
var formattedTypes = type.GetGenericArguments().Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
|
||||
|
||||
return $"({string.Join(", ", formattedTypes)})";
|
||||
}
|
||||
}
|
||||
|
||||
string displayName;
|
||||
|
||||
// Generic types
|
||||
if (type.Name.Contains('`'))
|
||||
{
|
||||
// Retrieve the current generic arguments for the current type (leaf or not)
|
||||
var tokens = type.Name.Split('`');
|
||||
var genericArgumentsCount = int.Parse(tokens[1]);
|
||||
var typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
|
||||
var currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
|
||||
var formattedTypes = currentTypeArguments.Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
|
||||
|
||||
// Standard generic types are displayed as Foo<T>
|
||||
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
|
||||
|
||||
// Track the current offset for the shared generic arguments list
|
||||
genericTypeOffset += genericArgumentsCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple custom types
|
||||
displayName = type.Name;
|
||||
}
|
||||
|
||||
// If the type is nested, recursively format the hierarchy as well
|
||||
if (type.IsNested)
|
||||
{
|
||||
var openDeclaringType = type.DeclaringType!;
|
||||
var rootGenericArguments = typeArguments.Slice(0, typeArguments.Length - genericTypeOffset).ToArray();
|
||||
|
||||
// If the declaring type is generic, we need to reconstruct the closed type
|
||||
// manually, as the declaring type instance doesn't retain type information.
|
||||
if (rootGenericArguments.Length > 0)
|
||||
{
|
||||
var closedDeclaringType = openDeclaringType.GetGenericTypeDefinition().MakeGenericType(rootGenericArguments);
|
||||
|
||||
return $"{FormatDisplayString(closedDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(openDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{type.Namespace}.{displayName}";
|
||||
}
|
||||
|
||||
// Atomically get or build the display string for the current type.
|
||||
return DisplayNames.GetValue(type, t =>
|
||||
{
|
||||
// By-ref types are displayed as T&
|
||||
if (t.IsByRef)
|
||||
{
|
||||
t = t.GetElementType()!;
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}&";
|
||||
}
|
||||
|
||||
// Pointer types are displayed as T*
|
||||
if (t.IsPointer)
|
||||
{
|
||||
int depth = 0;
|
||||
|
||||
// Calculate the pointer indirection level
|
||||
while (t.IsPointer)
|
||||
{
|
||||
depth++;
|
||||
t = t.GetElementType()!;
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}{new string('*', depth)}";
|
||||
}
|
||||
|
||||
// Standard path for concrete types
|
||||
return FormatDisplayString(t, 0, t.GetGenericArguments());
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not a given type is generic.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <returns>Whether or not the input type is generic.</returns>
|
||||
[Pure]
|
||||
private static bool IsGenericType(this Type type)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return type.GetTypeInfo().IsGenericType;
|
||||
#else
|
||||
return type.IsGenericType;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Returns an array of types representing the generic arguments.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <returns>An array of types representing the generic arguments.</returns>
|
||||
[Pure]
|
||||
private static Type[] GetGenericArguments(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GenericTypeParameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether <paramref name="type"/> is an instance of <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <param name="value">The type to check against.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="type"/> is an instance of <paramref name="value"/>, <see langword="false"/> otherwise.</returns>
|
||||
[Pure]
|
||||
internal static bool IsInstanceOfType(this Type type, object value)
|
||||
{
|
||||
return type.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with value types.
|
||||
/// </summary>
|
||||
public static class ValueTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the table of hex characters (doesn't allocate, maps to .text section, see <see href="https://github.com/dotnet/roslyn/pull/24621"/>).
|
||||
/// </summary>
|
||||
private static ReadOnlySpan<byte> HexCharactersTable => new[]
|
||||
{
|
||||
(byte)'0', (byte)'1', (byte)'2', (byte)'3',
|
||||
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
|
||||
(byte)'8', (byte)'9', (byte)'A', (byte)'B',
|
||||
(byte)'C', (byte)'D', (byte)'E', (byte)'F'
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hexadecimal <see cref="string"/> representation of a given <typeparamref name="T"/> value, left-padded and ordered as big-endian.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The input type to format to <see cref="string"/>.</typeparam>
|
||||
/// <param name="value">The input value to format to <see cref="string"/>.</param>
|
||||
/// <returns>
|
||||
/// The hexadecimal representation of <paramref name="value"/> (with the '0x' prefix), left-padded to byte boundaries and ordered as big-endian.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// As a byte (8 bits) is represented by two hexadecimal digits (each representing a group of 4 bytes), each <see cref="string"/>
|
||||
/// representation will always contain an even number of digits. For instance:
|
||||
/// <code>
|
||||
/// Console.WriteLine(1.ToHexString()); // "0x01"
|
||||
/// Console.WriteLine(((byte)255).ToHexString()); // "0xFF"
|
||||
/// Console.WriteLine((-1).ToHexString()); // "0xFFFFFFFF"
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[SkipLocalsInit]
|
||||
public static unsafe string ToHexString<T>(this T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
int
|
||||
sizeOfT = Unsafe.SizeOf<T>(),
|
||||
bufferSize = (2 * sizeOfT) + 2;
|
||||
char* p = stackalloc char[bufferSize];
|
||||
|
||||
p[0] = '0';
|
||||
p[1] = 'x';
|
||||
|
||||
ref byte rh = ref MemoryMarshal.GetReference(HexCharactersTable);
|
||||
|
||||
for (int i = 0, j = bufferSize - 2; i < sizeOfT; i++, j -= 2)
|
||||
{
|
||||
byte b = ((byte*)&value)[i];
|
||||
int
|
||||
low = b & 0x0F,
|
||||
high = (b & 0xF0) >> 4;
|
||||
|
||||
p[j + 1] = (char)Unsafe.Add(ref rh, low);
|
||||
p[j] = (char)Unsafe.Add(ref rh, high);
|
||||
}
|
||||
|
||||
return new string(p, 0, bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,301 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
<#@include file="TypeInfo.ttinclude" #>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
<#
|
||||
GenerateTextForItems(EnumerableTypes, item =>
|
||||
{
|
||||
#>
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must be empty.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is != 0.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsEmpty<T>(<#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsEmpty(<#=item.Cast#><#=item.Name#>, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must not be empty.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is == 0.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotEmpty<T>(<#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
<#
|
||||
if (item.Type == "Span<T>")
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithSpan<T>(name);
|
||||
<#
|
||||
}
|
||||
else if (item.Type == "ReadOnlySpan<T>")
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithReadOnlySpan<T>(name);
|
||||
<#
|
||||
}
|
||||
else
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEmpty<<#=item.Type#>>(name);
|
||||
<#
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size of a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is != <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> == size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size not equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is == <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeNotEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> != size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size over a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is <= <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeGreaterThan<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> > size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size of at least or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is < <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeGreaterThanOrEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size of less than a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is >= <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThan<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> < size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <#=item.XmlType#> instance must have a size of less than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="<#=item.Name#>"/> is > <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThanOrEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
if (<#=item.Name#>.<#=item.Size#> <= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#=item.Cast#><#=item.Name#>, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the source <#=item.XmlType#> instance must have the same size of a destination <#=item.XmlType#> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="source">The source <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="destination">The destination <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="source"/> is != the one of <paramref name="destination"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeEqualTo<T>(<#=item.Type#> source, <#=item.DestinationType#> destination, string name)
|
||||
{
|
||||
if (source.<#=item.Size#> == destination.<#=item.Size#>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
<#
|
||||
if (item.HasCountProperty)
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#>source, destination.<#=item.Size#>, name);
|
||||
<#
|
||||
}
|
||||
else
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, <#=item.Cast#>destination, name);
|
||||
<#
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the source <#=item.XmlType#> instance must have a size of less than or equal to that of a destination <#=item.XmlType#> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="source">The source <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="destination">The destination <#=item.XmlType#> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="source"/> is > the one of <paramref name="destination"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThanOrEqualTo<T>(<#=item.Type#> source, <#=item.DestinationType#> destination, string name)
|
||||
{
|
||||
if (source.<#=item.Size#> <= destination.<#=item.Size#>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
<#
|
||||
if (item.HasCountProperty)
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#>source, destination.<#=item.Size#>, name);
|
||||
<#
|
||||
}
|
||||
else
|
||||
{
|
||||
#>
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, <#=item.Cast#>destination, name);
|
||||
<#
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input index is valid for a given <#=item.XmlType#> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="index">The input index to be used to access <paramref name="<#=item.Name#>"/>.</param>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to use to validate <paramref name="index"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is not valid to access <paramref name="<#=item.Name#>"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsInRangeFor<T>(int index, <#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
<#
|
||||
// Here we're leveraging the fact that signed integers are represented
|
||||
// in 2-complement to perform the bounds check with a single compare operation.
|
||||
// This is the same trick used throughout CoreCLR as well.
|
||||
// For more info and code sample, see the original conversation here:
|
||||
// https://github.com/CommunityToolkit/WindowsCommunityToolkit/pull/3131#discussion_r390682835
|
||||
#>
|
||||
if ((uint)index < (uint)<#=item.Name#>.<#=item.Size#>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, <#=item.Cast#><#=item.Name#>, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input index is not valid for a given <#=item.XmlType#> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <#=item.XmlType#> instance.</typeparam>
|
||||
/// <param name="index">The input index to be used to access <paramref name="<#=item.Name#>"/>.</param>
|
||||
/// <param name="<#=item.Name#>">The input <#=item.XmlType#> instance to use to validate <paramref name="index"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is valid to access <paramref name="<#=item.Name#>"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotInRangeFor<T>(int index, <#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
if ((uint)index >= (uint)<#=item.Name#>.<#=item.Size#>)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, <#=item.Cast#><#=item.Name#>, name);
|
||||
}
|
||||
<#
|
||||
});
|
||||
#>
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,274 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
<#@include file="TypeInfo.ttinclude" #>
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
<#
|
||||
GenerateTextForItems(NumericTypes, typeInfo =>
|
||||
{
|
||||
var (type, prefix) = typeInfo;
|
||||
#>
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="target">The target <see <#=prefix#>="<#=type#>"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is != <paramref name="target"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsEqualTo(<#=type#> value, <#=type#> target, string name)
|
||||
{
|
||||
if (value == target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsEqualTo(value, target, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be not equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="target">The target <see <#=prefix#>="<#=type#>"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is == <paramref name="target"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotEqualTo(<#=type#> value, <#=type#> target, string name)
|
||||
{
|
||||
if (value != target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEqualTo(value, target, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be less than a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="maximum">The exclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsLessThan(<#=type#> value, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value < maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThan(value, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be less than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="maximum">The inclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is > <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsLessThanOrEqualTo(<#=type#> value, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value <= maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(value, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be greater than a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is <= <paramref name="minimum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsGreaterThan(<#=type#> value, <#=type#> minimum, string name)
|
||||
{
|
||||
if (value > minimum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThan(value, minimum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be greater than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsGreaterThanOrEqualTo(<#=type#> value, <#=type#> minimum, string name)
|
||||
{
|
||||
if (value >= minimum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(value, minimum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given range.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/> or >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in [<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value >= minimum && value < maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRange(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given range.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="minimum"/> or < <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in [<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value < minimum || value >= maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRange(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given interval.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is <= <paramref name="minimum"/> or >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in (<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value > minimum && value < maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetween(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given interval.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is > <paramref name="minimum"/> or < <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in (<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value <= minimum || value >= maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetween(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given interval.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The inclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/> or > <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in [<paramref name="minimum"/>, <paramref name="maximum"/>]", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value >= minimum && value <= maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given interval.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see <#=prefix#>="<#=type#>"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The inclusive maximum <see <#=prefix#>="<#=type#>"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="minimum"/> or <= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in [<paramref name="minimum"/>, <paramref name="maximum"/>]", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, string name)
|
||||
{
|
||||
if (value < minimum || value > maximum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(value, minimum, maximum, name);
|
||||
}
|
||||
<#
|
||||
});
|
||||
#>
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
# T4 templates and generated APIs
|
||||
|
||||
This folder contains a number of template files (with the `.tt` or `.ttinclude` extensions) and the generated `.g.cs` files generated by those templates. The template files use the T4 format, which is natively supported by Visual Studio (more info is available [here](https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2019)).
|
||||
|
||||
## Why is this needed?
|
||||
|
||||
There are a few reasons why T4 templates are used for the `Guard` class:
|
||||
|
||||
- Especially when a large number of similar overloads are available for the same APIs, using templates makes it much easier to maintain code and spot mistakes, as the actual number of lines of code to review is much smaller: it's just the code in each template!
|
||||
- Using type-specific overloads instead of generic methods can result in faster code. For instance, T4 templates are used to generate overloads for comparison APIs (eg. `Guard.IsGreaterThan(int, int, string)`). This results in more compact and optimized code as opposed to a generic method using `where T : IComparable<T>` as type constraint.
|
||||
- In some cases, using generic methods just isn't possible. For instance, types like `Span<T>` and `ReadOnlySpan<T>` can't be used as generic type parameters, and even if that had been possible, they don't implement an interface we could use in the generic type constraint. Using T4 templates solves this issue, as we can just have specialized method for each supported type or collection type.
|
||||
|
||||
## How to make changes
|
||||
|
||||
If you need to change an API that is declared in a template, or to add a new one, just edit the right `.tt` file and save it: Visual Studio will take care of updating the generated `.g.cs` file automatically. Don't make changes to those generated `.g.cs` files directly, as those will be overwritten as soon as their source template is updated.
|
||||
|
||||
Note that all the `.g.cs` files are checked in into the repository, so if you do make changes to a template file, make sure to also include the updated `.g.cs` file in your commits.
|
|
@ -1,817 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// =====================
|
||||
// Auto generated file
|
||||
// =====================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(Span<T> span, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must be empty, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(Span<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(Span<T> source, Span<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(Span<T> source, Span<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, Span<T> span, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(span.Length)} to be a valid index for the target collection ({typeof(Span<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, Span<T> span, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(span.Length)} to be an invalid index for the target collection ({typeof(Span<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(ReadOnlySpan<T> span, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must be empty, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ReadOnlySpan<T> span, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(span.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ReadOnlySpan<T> source, Span<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ReadOnlySpan<T> source, Span<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, ReadOnlySpan<T> span, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(span.Length)} to be a valid index for the target collection ({typeof(ReadOnlySpan<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, ReadOnlySpan<T> span, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(span.Length)} to be an invalid index for the target collection ({typeof(ReadOnlySpan<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(Memory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must be empty, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(Memory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(Memory<T> source, Memory<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(Memory<T> source, Memory<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(Memory<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, Memory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(memory.Length)} to be a valid index for the target collection ({typeof(Memory<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, Memory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(memory.Length)} to be an invalid index for the target collection ({typeof(Memory<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(ReadOnlyMemory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must be empty, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ReadOnlyMemory<T> memory, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(memory.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ReadOnlyMemory<T> source, Memory<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ReadOnlyMemory<T> source, Memory<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ReadOnlyMemory<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, ReadOnlyMemory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(memory.Length)} to be a valid index for the target collection ({typeof(ReadOnlyMemory<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, ReadOnlyMemory<T> memory, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(memory.Length)} to be an invalid index for the target collection ({typeof(ReadOnlyMemory<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(T[] array, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must be empty, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size over {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(T[] array, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(array.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(T[] source, T[] destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(T[] source, T[] destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(T[]).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, T[] array, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(array.Length)} to be a valid index for the target collection ({typeof(T[]).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, T[] array, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(array.Length)} to be an invalid index for the target collection ({typeof(T[]).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(List<T> list, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must be empty, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(List<T> list, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(list.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(List<T> source, List<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(List<T> source, List<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(List<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, List<T> list, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(list.Count)} to be a valid index for the target collection ({typeof(List<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, List<T> list, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(list.Count)} to be an invalid index for the target collection ({typeof(List<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(ICollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must be empty, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ICollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(ICollection<T> source, ICollection<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(ICollection<T> source, ICollection<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(ICollection<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, ICollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(collection.Count)} to be a valid index for the target collection ({typeof(ICollection<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, ICollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(collection.Count)} to be an invalid index for the target collection ({typeof(ICollection<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(IReadOnlyCollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must be empty, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(IReadOnlyCollection<T> collection, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(collection.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(IReadOnlyCollection<T> source, ICollection<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(IReadOnlyCollection<T> source, ICollection<T> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(IReadOnlyCollection<T>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.Count)} (the destination), had a size of {AssertString(source.Count)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, IReadOnlyCollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(collection.Count)} to be a valid index for the target collection ({typeof(IReadOnlyCollection<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, IReadOnlyCollection<T> collection, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(collection.Count)} to be an invalid index for the target collection ({typeof(IReadOnlyCollection<T>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
<#@include file="TypeInfo.ttinclude" #>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
<#
|
||||
GenerateTextForItems(EnumerableTypes, item =>
|
||||
{
|
||||
#>
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty<T>(<#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must be empty, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],int,string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(<#=item.Type#> <#=item.Name#>, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo<T>(<#=item.Type#> source, <#=item.DestinationType#> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size equal to {AssertString(destination.<#=item.Size#>)} (the destination), had a size of {AssertString(source.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo{T}(T[],T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo<T>(<#=item.Type#> source, <#=item.DestinationType#> destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.<#=item.Size#>)} (the destination), had a size of {AssertString(source.<#=item.Size#>)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor<T>(int index, <#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(<#=item.Name#>.<#=item.Size#>)} to be a valid index for the target collection ({typeof(<#=item.Type#>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor{T}(int,T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor<T>(int index, <#=item.Type#> <#=item.Name#>, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(<#=item.Name#>.<#=item.Size#>)} to be an invalid index for the target collection ({typeof(<#=item.Type#>).ToTypeString()}), was {AssertString(index)}");
|
||||
}
|
||||
<#
|
||||
});
|
||||
#>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
<#@ template language="C#"#>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".g.cs"#>
|
||||
// =====================
|
||||
// Auto generated file
|
||||
// =====================
|
||||
<#+
|
||||
/// <summary>
|
||||
/// A model representing the info on an enumerable type
|
||||
/// </summary>
|
||||
sealed class EnumerableTypeInfo
|
||||
{
|
||||
public EnumerableTypeInfo(
|
||||
string type,
|
||||
string xmlType,
|
||||
string name,
|
||||
string size,
|
||||
string destinationType,
|
||||
string cast)
|
||||
{
|
||||
Type = type;
|
||||
XmlType = xmlType;
|
||||
Name = name;
|
||||
Size = size;
|
||||
DestinationType = destinationType;
|
||||
Cast = cast;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the current type
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML-formatted name of the current type (eg. with {T} instead of <T>)
|
||||
/// </summary>
|
||||
public string XmlType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variable name to use
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the property to use to retrieve the length of the current type
|
||||
/// </summary>
|
||||
public string Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the current type has a "Count" property
|
||||
/// </summary>
|
||||
public bool HasCountProperty => Size == "Count";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the destination type, when comparing counts across different collections
|
||||
/// </summary>
|
||||
public string DestinationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the (optional) casting to resolve the diamond problem between different interfaces
|
||||
/// </summary>
|
||||
public string Cast { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of available enumerable types to generate APIs for
|
||||
/// </summary>
|
||||
static readonly IReadOnlyList<EnumerableTypeInfo> EnumerableTypes = new[]
|
||||
{
|
||||
new EnumerableTypeInfo("Span<T>", "<see cref=\"Span{T}\"/>", "span", "Length", "Span<T>", ""),
|
||||
new EnumerableTypeInfo("ReadOnlySpan<T>", "<see cref=\"ReadOnlySpan{T}\"/>", "span", "Length", "Span<T>", ""),
|
||||
new EnumerableTypeInfo("Memory<T>", "<see cref=\"Memory{T}\"/>", "memory", "Length", "Memory<T>", ""),
|
||||
new EnumerableTypeInfo("ReadOnlyMemory<T>", "<see cref=\"ReadOnlyMemory{T}\"/>", "memory", "Length", "Memory<T>", ""),
|
||||
new EnumerableTypeInfo("T[]", "<see typeparamref=\"T\"/> array", "array", "Length", "T[]", ""),
|
||||
new EnumerableTypeInfo("List<T>", "<see cref=\"List{T}\"/>", "list", "Count", "List<T>", "(ICollection<T>)"),
|
||||
new EnumerableTypeInfo("ICollection<T>", "<see cref=\"ICollection{T}\"/>", "collection", "Count", "ICollection<T>", ""),
|
||||
new EnumerableTypeInfo("IReadOnlyCollection<T>", "<see cref=\"IReadOnlyCollection{T}\"/>", "collection", "Count", "ICollection<T>", ""),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of available numeric types to generate APIs for
|
||||
/// </summary>
|
||||
static readonly IReadOnlyList<(string Name, string Prefix)> NumericTypes = new[]
|
||||
{
|
||||
("byte", "cref"),
|
||||
("sbyte", "cref"),
|
||||
("short", "cref"),
|
||||
("ushort", "cref"),
|
||||
("char", "cref"),
|
||||
("int", "cref"),
|
||||
("uint", "cref"),
|
||||
("float", "cref"),
|
||||
("long", "cref"),
|
||||
("ulong", "cref"),
|
||||
("double", "cref"),
|
||||
("decimal", "cref"),
|
||||
("nint", "langword"),
|
||||
("nuint", "langword")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Generates text for a given sequence of items, automatically adding the necessary spacing
|
||||
/// </summary>
|
||||
void GenerateTextForItems<T>(IReadOnlyList<T> items, Action<T> factory)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
// Insert a blank line after the first item
|
||||
if (i > 0) WriteLine("");
|
||||
|
||||
// Invoke the factory with the current item
|
||||
factory(items [i]);
|
||||
}
|
||||
}
|
||||
#>
|
|
@ -1,445 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input value is <see langword="default"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see langword="struct"/> value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not <see langword="default"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsDefault<T>(T value, string name)
|
||||
where T : struct, IEquatable<T>
|
||||
{
|
||||
if (value.Equals(default))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsDefault(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is not <see langword="default"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see langword="struct"/> value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is <see langword="default"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotDefault<T>(T value, string name)
|
||||
where T : struct, IEquatable<T>
|
||||
{
|
||||
if (!value.Equals(default))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotDefault<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="target">The target <typeparamref name="T"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is != <paramref name="target"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsEqualTo<T>(T value, T target, string name)
|
||||
where T : notnull, IEquatable<T>
|
||||
{
|
||||
if (value.Equals(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsEqualTo(value, target, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be not equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="target">The target <typeparamref name="T"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is == <paramref name="target"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotEqualTo<T>(T value, T target, string name)
|
||||
where T : notnull, IEquatable<T>
|
||||
{
|
||||
if (!value.Equals(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEqualTo(value, target, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be a bitwise match with a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="target">The target <typeparamref name="T"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not a bitwise match for <paramref name="target"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void IsBitwiseEqualTo<T>(T value, T target, string name)
|
||||
where T : unmanaged
|
||||
{
|
||||
// Include some fast paths if the input type is of size 1, 2, 4, 8, or 16.
|
||||
// In those cases, just reinterpret the bytes as values of an integer type,
|
||||
// and compare them directly, which is much faster than having a loop over each byte.
|
||||
// The conditional branches below are known at compile time by the JIT compiler,
|
||||
// so that only the right one will actually be translated into native code.
|
||||
if (sizeof(T) == 1)
|
||||
{
|
||||
byte valueByte = Unsafe.As<T, byte>(ref value);
|
||||
byte targetByte = Unsafe.As<T, byte>(ref target);
|
||||
|
||||
if (valueByte == targetByte)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
else if (sizeof(T) == 2)
|
||||
{
|
||||
ushort valueUShort = Unsafe.As<T, ushort>(ref value);
|
||||
ushort targetUShort = Unsafe.As<T, ushort>(ref target);
|
||||
|
||||
if (valueUShort == targetUShort)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
else if (sizeof(T) == 4)
|
||||
{
|
||||
uint valueUInt = Unsafe.As<T, uint>(ref value);
|
||||
uint targetUInt = Unsafe.As<T, uint>(ref target);
|
||||
|
||||
if (valueUInt == targetUInt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
else if (sizeof(T) == 8)
|
||||
{
|
||||
ulong valueULong = Unsafe.As<T, ulong>(ref value);
|
||||
ulong targetULong = Unsafe.As<T, ulong>(ref target);
|
||||
|
||||
if (Bit64Compare(ref valueULong, ref targetULong))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
else if (sizeof(T) == 16)
|
||||
{
|
||||
ulong valueULong0 = Unsafe.As<T, ulong>(ref value);
|
||||
ulong targetULong0 = Unsafe.As<T, ulong>(ref target);
|
||||
|
||||
if (Bit64Compare(ref valueULong0, ref targetULong0))
|
||||
{
|
||||
ulong valueULong1 = Unsafe.Add(ref Unsafe.As<T, ulong>(ref value), 1);
|
||||
ulong targetULong1 = Unsafe.Add(ref Unsafe.As<T, ulong>(ref target), 1);
|
||||
|
||||
if (Bit64Compare(ref valueULong1, ref targetULong1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> valueBytes = new Span<byte>(Unsafe.AsPointer(ref value), sizeof(T));
|
||||
Span<byte> targetBytes = new Span<byte>(Unsafe.AsPointer(ref target), sizeof(T));
|
||||
|
||||
if (valueBytes.SequenceEqual(targetBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
}
|
||||
|
||||
// Compares 64 bits of data from two given memory locations for bitwise equality
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe bool Bit64Compare(ref ulong left, ref ulong right)
|
||||
{
|
||||
// Handles 32 bit case, because using ulong is inefficient
|
||||
if (sizeof(IntPtr) == 4)
|
||||
{
|
||||
ref int r0 = ref Unsafe.As<ulong, int>(ref left);
|
||||
ref int r1 = ref Unsafe.As<ulong, int>(ref right);
|
||||
|
||||
return r0 == r1 &&
|
||||
Unsafe.Add(ref r0, 1) == Unsafe.Add(ref r1, 1);
|
||||
}
|
||||
|
||||
return left == right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be less than a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="maximum">The exclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsLessThan<T>(T value, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(maximum) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThan(value, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be less than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="maximum">The inclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is > <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsLessThanOrEqualTo<T>(T value, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(maximum) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(value, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be greater than a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is <= <paramref name="minimum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsGreaterThan<T>(T value, T minimum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThan(value, minimum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be greater than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsGreaterThanOrEqualTo<T>(T value, T minimum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(value, minimum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/> or >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in [<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsInRange<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRange(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given range.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="minimum"/> or < <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in [<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotInRange<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRange(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is <= <paramref name="minimum"/> or >= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in (<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsBetween<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) > 0 && value.CompareTo(maximum) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetween(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The exclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The exclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is > <paramref name="minimum"/> or < <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in (<paramref name="minimum"/>, <paramref name="maximum"/>)", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotBetween<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) <= 0 || value.CompareTo(maximum) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetween(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be in a given interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The inclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is < <paramref name="minimum"/> or > <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> in [<paramref name="minimum"/>, <paramref name="maximum"/>]", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsBetweenOrEqualTo<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(value, minimum, maximum, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be in a given interval.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="minimum">The inclusive minimum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="maximum">The inclusive maximum <typeparamref name="T"/> value that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> is >= <paramref name="minimum"/> or <= <paramref name="maximum"/>.</exception>
|
||||
/// <remarks>
|
||||
/// This API asserts the equivalent of "<paramref name="value"/> not in [<paramref name="minimum"/>, <paramref name="maximum"/>]", using arithmetic notation.
|
||||
/// The method is generic to avoid boxing the parameters, if they are value types.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotBetweenOrEqualTo<T>(T value, T minimum, T maximum, string name)
|
||||
where T : notnull, IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(value, minimum, maximum, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,271 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="int"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="int"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) > <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCloseTo(int value, int target, uint delta, string name)
|
||||
{
|
||||
uint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (uint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (uint)(target - value);
|
||||
}
|
||||
|
||||
if (difference <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="int"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="int"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) <= <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCloseTo(int value, int target, uint delta, string name)
|
||||
{
|
||||
uint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (uint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (uint)(target - value);
|
||||
}
|
||||
|
||||
if (difference > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="long"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="long"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) > <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCloseTo(long value, long target, ulong delta, string name)
|
||||
{
|
||||
ulong difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (ulong)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (ulong)(target - value);
|
||||
}
|
||||
|
||||
if (difference <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="long"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="long"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) <= <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCloseTo(long value, long target, ulong delta, string name)
|
||||
{
|
||||
ulong difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (ulong)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (ulong)(target - value);
|
||||
}
|
||||
|
||||
if (difference > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="float"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="float"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) > <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCloseTo(float value, float target, float delta, string name)
|
||||
{
|
||||
if (Math.Abs(value - target) <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="float"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="float"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) <= <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCloseTo(float value, float target, float delta, string name)
|
||||
{
|
||||
if (Math.Abs(value - target) > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="double"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="double"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) > <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCloseTo(double value, double target, double delta, string name)
|
||||
{
|
||||
if (Math.Abs(value - target) <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="double"/> value to test.</param>
|
||||
/// <param name="target">The target <see cref="double"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) <= <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCloseTo(double value, double target, double delta, string name)
|
||||
{
|
||||
if (Math.Abs(value - target) > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see langword="nint"/> value to test.</param>
|
||||
/// <param name="target">The target <see langword="nint"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) > <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCloseTo(nint value, nint target, nuint delta, string name)
|
||||
{
|
||||
nuint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (nuint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (nuint)(target - value);
|
||||
}
|
||||
|
||||
if (difference <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see langword="nint"/> value to test.</param>
|
||||
/// <param name="target">The target <see langword="nint"/> value to test for.</param>
|
||||
/// <param name="delta">The maximum distance to allow between <paramref name="value"/> and <paramref name="target"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if (<paramref name="value"/> - <paramref name="target"/>) <= <paramref name="delta"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCloseTo(nint value, nint target, nuint delta, string name)
|
||||
{
|
||||
nuint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (nuint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (nuint)(target - value);
|
||||
}
|
||||
|
||||
if (difference > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Stream"/> instance must support reading.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input <see cref="Stream"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="stream"/> doesn't support reading.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CanRead(Stream stream, string name)
|
||||
{
|
||||
if (stream.CanRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForCanRead(stream, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Stream"/> instance must support writing.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input <see cref="Stream"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="stream"/> doesn't support writing.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CanWrite(Stream stream, string name)
|
||||
{
|
||||
if (stream.CanWrite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForCanWrite(stream, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Stream"/> instance must support seeking.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input <see cref="Stream"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="stream"/> doesn't support seeking.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CanSeek(Stream stream, string name)
|
||||
{
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForCanSeek(stream, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Stream"/> instance must be at the starting position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input <see cref="Stream"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="stream"/> is not at the starting position.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsAtStartPosition(Stream stream, string name)
|
||||
{
|
||||
if (stream.Position == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsAtStartPosition(stream, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,410 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS8777
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be <see langword="null"/> or empty.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is neither <see langword="null"/> nor empty.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNullOrEmpty(string? text, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNullOrEmpty(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be <see langword="null"/> or empty.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="text"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is empty.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotNullOrEmpty([NotNull] string? text, string name)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotNullOrEmpty(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is neither <see langword="null"/> nor whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNullOrWhiteSpace(string? text, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNullOrWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is neither <see langword="null"/> nor whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use " + nameof(IsNullOrWhiteSpace))]
|
||||
public static void IsNullOrWhitespace(string? text, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNullOrWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="text"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotNullOrWhiteSpace([NotNull] string? text, string name)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotNullOrWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is <see langword="null"/> or whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use " + nameof(IsNotNullOrWhiteSpace))]
|
||||
public static void IsNotNullOrWhitespace([NotNull] string? text, string name)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotNullOrWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be empty.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is empty.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsEmpty(string text, string name)
|
||||
{
|
||||
if (text.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsEmpty(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be empty.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is empty.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotEmpty(string text, string name)
|
||||
{
|
||||
if (text.Length != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEmpty(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is neither <see langword="null"/> nor whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsWhiteSpace(string text, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must be whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is neither <see langword="null"/> nor whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use " + nameof(IsWhiteSpace))]
|
||||
public static void IsWhitespace(string text, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is <see langword="null"/> or whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotWhiteSpace(string text, string name)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must not be <see langword="null"/> or whitespace.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="text"/> is <see langword="null"/> or whitespace.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use " + nameof(IsNotWhiteSpace))]
|
||||
public static void IsNotWhitespace(string text, string name)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotWhiteSpace(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size of a specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is != <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeEqualTo(string text, int size, string name)
|
||||
{
|
||||
if (text.Length == size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size not equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is == <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeNotEqualTo(string text, int size, string name)
|
||||
{
|
||||
if (text.Length != size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size over a specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is <= <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeGreaterThan(string text, int size, string name)
|
||||
{
|
||||
if (text.Length > size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size of at least specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is < <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeGreaterThanOrEqualTo(string text, int size, string name)
|
||||
{
|
||||
if (text.Length >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size of less than a specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is >= <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThan(string text, int size, string name)
|
||||
{
|
||||
if (text.Length < size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="string"/> instance must have a size of less than or equal to a specified value.
|
||||
/// </summary>
|
||||
/// <param name="text">The input <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="size">The target size to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="text"/> is > <paramref name="size"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThanOrEqualTo(string text, int size, string name)
|
||||
{
|
||||
if (text.Length <= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(text, size, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the source <see cref="string"/> instance must have the same size of a destination <see cref="string"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="destination">The destination <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="source"/> is != the one of <paramref name="destination"/>.</exception>
|
||||
/// <remarks>The <see cref="string"/> type is immutable, but the name of this API is kept for consistency with the other overloads.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeEqualTo(string source, string destination, string name)
|
||||
{
|
||||
if (source.Length == destination.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, destination, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the source <see cref="string"/> instance must have a size of less than or equal to that of a destination <see cref="string"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="destination">The destination <see cref="string"/> instance to check the size for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the size of <paramref name="source"/> is > the one of <paramref name="destination"/>.</exception>
|
||||
/// <remarks>The <see cref="string"/> type is immutable, but the name of this API is kept for consistency with the other overloads.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasSizeLessThanOrEqualTo(string source, string destination, string name)
|
||||
{
|
||||
if (source.Length <= destination.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, destination, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input index is valid for a given <see cref="string"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="index">The input index to be used to access <paramref name="text"/>.</param>
|
||||
/// <param name="text">The input <see cref="string"/> instance to use to validate <paramref name="index"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is not valid to access <paramref name="text"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsInRangeFor(int index, string text, string name)
|
||||
{
|
||||
if ((uint)index < (uint)text.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input index is not valid for a given <see cref="string"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="index">The input index to be used to access <paramref name="text"/>.</param>
|
||||
/// <param name="text">The input <see cref="string"/> instance to use to validate <paramref name="index"/>.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="index"/> is valid to access <paramref name="text"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotInRangeFor(int index, string text, string name)
|
||||
{
|
||||
if ((uint)index >= (uint)text.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, text, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is in a completed state.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is not in a completed state.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCompleted(Task task, string name)
|
||||
{
|
||||
if (task.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCompleted(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is not in a completed state.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is in a completed state.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCompleted(Task task, string name)
|
||||
{
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCompleted(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance has been completed successfully.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> has not been completed successfully.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCompletedSuccessfully(Task task, string name)
|
||||
{
|
||||
if (task.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCompletedSuccessfully(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance has not been completed successfully.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> has been completed successfully.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCompletedSuccessfully(Task task, string name)
|
||||
{
|
||||
if (task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCompletedSuccessfully(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is faulted.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is not faulted.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsFaulted(Task task, string name)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsFaulted(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is not faulted.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is faulted.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotFaulted(Task task, string name)
|
||||
{
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotFaulted(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is canceled.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is not canceled.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsCanceled(Task task, string name)
|
||||
{
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCanceled(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance is not canceled.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> is canceled.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotCanceled(Task task, string name)
|
||||
{
|
||||
if (!task.IsCanceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCanceled(task, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance has a specific status.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="status">The task status that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> doesn't match <paramref name="status"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasStatusEqualTo(Task task, TaskStatus status, string name)
|
||||
{
|
||||
if (task.Status == status)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasStatusEqualTo(task, status, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input <see cref="Task"/> instance has not a specific status.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to test.</param>
|
||||
/// <param name="status">The task status that is accepted.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="task"/> matches <paramref name="status"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void HasStatusNotEqualTo(Task task, TaskStatus status, string name)
|
||||
{
|
||||
if (task.Status != status)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForHasStatusNotEqualTo(task, status, name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts that the input value is <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not <see langword="null"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNull<T>(T? value, string name)
|
||||
where T : class
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNull(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of nullable value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not <see langword="null"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNull<T>(T? value, string name)
|
||||
where T : struct
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNull(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotNull<T>([NotNull] T? value, string name)
|
||||
where T : class
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentNullExceptionForIsNotNull<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of nullable value type being tested.</typeparam>
|
||||
/// <param name="value">The input value to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>The method is generic to avoid boxing the parameters, if they are value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotNull<T>([NotNull] T? value, string name)
|
||||
where T : struct
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentNullExceptionForIsNotNull<T?>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is of a specific type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not of type <typeparamref name="T"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsOfType<T>(object value, string name)
|
||||
{
|
||||
if (value.GetType() == typeof(T))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsOfType<T>(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is not of a specific type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is of type <typeparamref name="T"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotOfType<T>(object value, string name)
|
||||
{
|
||||
if (value.GetType() != typeof(T))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotOfType<T>(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="type">The type to look for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the type of <paramref name="value"/> is not the same as <paramref name="type"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsOfType(object value, Type type, string name)
|
||||
{
|
||||
if (value.GetType() == type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsOfType(value, type, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value is not of a specific type.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="type">The type to look for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the type of <paramref name="value"/> is the same as <paramref name="type"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotOfType(object value, Type type, string name)
|
||||
{
|
||||
if (value.GetType() != type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotOfType(value, type, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value can be assigned to a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to check the input value against.</typeparam>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> can't be assigned to type <typeparamref name="T"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsAssignableToType<T>(object value, string name)
|
||||
{
|
||||
if (value is T)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsAssignableToType<T>(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value can't be assigned to a specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to check the input value against.</typeparam>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> can be assigned to type <typeparamref name="T"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotAssignableToType<T>(object value, string name)
|
||||
{
|
||||
if (value is not T)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotAssignableToType<T>(value, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value can be assigned to a specified type.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="type">The type to look for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> can't be assigned to <paramref name="type"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsAssignableToType(object value, Type type, string name)
|
||||
{
|
||||
if (type.IsInstanceOfType(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsAssignableToType(value, type, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value can't be assigned to a specified type.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="object"/> to test.</param>
|
||||
/// <param name="type">The type to look for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> can be assigned to <paramref name="type"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsNotAssignableToType(object value, Type type, string name)
|
||||
{
|
||||
if (!type.IsInstanceOfType(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotAssignableToType(value, type, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be the same instance as the target value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="target">The target <typeparamref name="T"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not the same instance as <paramref name="target"/>.</exception>
|
||||
/// <remarks>The method is generic to prevent using it with value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsReferenceEqualTo<T>(T value, T target, string name)
|
||||
where T : class
|
||||
{
|
||||
if (ReferenceEquals(value, target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsReferenceEqualTo<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must not be the same instance as the target value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values to compare.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to test.</param>
|
||||
/// <param name="target">The target <typeparamref name="T"/> value to test for.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is the same instance as <paramref name="target"/>.</exception>
|
||||
/// <remarks>The method is generic to prevent using it with value types.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsReferenceNotEqualTo<T>(T value, T target, string name)
|
||||
where T : class
|
||||
{
|
||||
if (!ReferenceEquals(value, target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsReferenceNotEqualTo<T>(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="bool"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsTrue([DoesNotReturnIf(false)] bool value, string name)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsTrue(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="bool"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <param name="message">A message to display if <paramref name="value"/> is <see langword="false"/>.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, string message)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsTrue(name, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be <see langword="false"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="bool"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is <see langword="true"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsFalse([DoesNotReturnIf(true)] bool value, string name)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsFalse(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that the input value must be <see langword="false"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="bool"/> to test.</param>
|
||||
/// <param name="name">The name of the input parameter being tested.</param>
|
||||
/// <param name="message">A message to display if <paramref name="value"/> is <see langword="true"/>.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is <see langword="true"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void IsFalse([DoesNotReturnIf(true)] bool value, string name, string message)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsFalse(name, message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEmpty{T}(Span{T},string)"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <remarks>This method is needed because <see cref="Span{T}"/> can't be used as a generic type parameter.</remarks>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotEmptyWithSpan<T>(string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span<T>).ToTypeString()}) must not be empty", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEmpty{T}(ReadOnlySpan{T},string)"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <remarks>This method is needed because <see cref="ReadOnlySpan{T}"/> can't be used as a generic type parameter.</remarks>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotEmptyWithReadOnlySpan<T>(string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan<T>).ToTypeString()}) must not be empty", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEmpty{T}(T[],string)"/> (or an overload) fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item of items in the input collection.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotEmpty<T>(string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be empty", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsDefault{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see langword="struct"/> value type being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsDefault<T>(T value, string name)
|
||||
where T : struct
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the default value {AssertString(default(T))}, was {AssertString(value)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotDefault{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see langword="struct"/> value type being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotDefault<T>(string name)
|
||||
where T : struct
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the default value {AssertString(default(T))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEqualTo<T>(T value, T target, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be equal to {AssertString(target)}, was {AssertString(value)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotEqualTo<T>(T value, T target, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be equal to {AssertString(target)}, was {AssertString(value)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsBitwiseEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input values being compared.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForBitwiseEqualTo<T>(T value, T target, string name)
|
||||
where T : unmanaged
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) is not a bitwise match, was <{value.ToHexString()}> instead of <{target.ToHexString()}>", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsLessThan{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsLessThan<T>(T value, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsLessThanOrEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo<T>(T value, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than or equal to {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsGreaterThan{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThan<T>(T value, T minimum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than {AssertString(minimum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsGreaterThanOrEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo<T>(T value, T minimum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than or equal to {AssertString(minimum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRange{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRange<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRange{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRange<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsBetween{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsBetween<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotBetween{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotBetween<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsBetweenOrEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotBetweenOrEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values being tested.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo<T>(T value, T minimum, T maximum, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(int,int,uint,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(int value, int target, uint delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCloseTo(int,int,uint,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCloseTo(int value, int target, uint delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(long,long,ulong,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(long value, long target, ulong delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCloseTo(long,long,ulong,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCloseTo(long value, long target, ulong delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(float,float,float,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(float value, float target, float delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCloseTo(float,float,float,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCloseTo(float value, float target, float delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(double,double,double,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(double value, double target, double delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCloseTo(double,double,double,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCloseTo(double value, double target, double delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(nint,nint,nuint,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(nint value, nint target, nuint delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCloseTo(nint,nint,nuint,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCloseTo(nint value, nint target, nuint delta, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.CanRead"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForCanRead(Stream stream, string name)
|
||||
{
|
||||
throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support reading", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.CanWrite"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForCanWrite(Stream stream, string name)
|
||||
{
|
||||
throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support writing", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.CanSeek"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForCanSeek(Stream stream, string name)
|
||||
{
|
||||
throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support seeking", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsAtStartPosition"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsAtStartPosition(Stream stream, string name)
|
||||
{
|
||||
throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) must be at position {AssertString(0)}, was at {AssertString(stream.Position)}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNullOrEmpty"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNullOrEmpty(string? text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must be null or empty, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentNullException"/> or <see cref="ArgumentException"/> when <see cref="Guard.IsNotNullOrEmpty"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotNullOrEmpty(string? text, string name)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static Exception GetException(string? text, string name)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or empty, was null");
|
||||
}
|
||||
|
||||
return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or empty, was empty", name);
|
||||
}
|
||||
|
||||
throw GetException(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNullOrWhitespace"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNullOrWhiteSpace(string? text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must be null or whitespace, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotNullOrWhitespace"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotNullOrWhiteSpace(string? text, string name)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static Exception GetException(string? text, string name)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or whitespace, was null");
|
||||
}
|
||||
|
||||
return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or whitespace, was whitespace", name);
|
||||
}
|
||||
|
||||
throw GetException(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsEmpty"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsEmpty(string text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must be empty, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEmpty"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotEmpty(string text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must not be empty", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsWhitespace"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsWhiteSpace(string text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must be whitespace, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotWhitespace"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotWhiteSpace(string text, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must not be whitespace, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo(string,int,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size equal to {size}, had a size of {text.Length} and was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeNotEqualTo"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeNotEqualTo(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must not have a size equal to {size}, was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThan"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThan(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size over {size}, had a size of {text.Length} and was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeGreaterThanOrEqualTo"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size of at least {size}, had a size of {text.Length} and was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThan"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThan(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size less than {size}, had a size of {text.Length} and was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo(string,int,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(string text, int size, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size less than or equal to {size}, had a size of {text.Length} and was {AssertString(text)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeEqualTo(string,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeEqualTo(string source, string destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} (string) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasSizeLessThanOrEqualTo(string,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(string source, string destination, string name)
|
||||
{
|
||||
throw new ArgumentException($"The source {AssertString(name)} (string) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsInRangeFor(int,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor(int index, string text, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(text.Length)} to be a valid index for the target string, was {AssertString(index)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Guard.IsNotInRangeFor(int,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(int index, string text, string name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(text.Length)} to be an invalid index for the target string, was {AssertString(index)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCompleted"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCompleted(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCompleted"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCompleted(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCompletedSuccessfully"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCompletedSuccessfully(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed successfully, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCompletedSuccessfully"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCompletedSuccessfully(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed successfully, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsFaulted"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsFaulted(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be faulted, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotFaulted"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotFaulted(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be faulted, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCanceled"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCanceled(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be canceled, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotCanceled"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotCanceled(Task task, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be canceled, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasStatusEqualTo"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasStatusEqualTo(Task task, TaskStatus status, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must have status {status}, had status {AssertString(task.Status)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.HasStatusNotEqualTo"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForHasStatusNotEqualTo(Task task, TaskStatus status, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not have status {AssertString(status)}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a formatted representation of the input value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The input <see cref="object"/> to format.</param>
|
||||
/// <returns>A formatted representation of <paramref name="obj"/> to display in error messages.</returns>
|
||||
[Pure]
|
||||
private static string AssertString(object? obj)
|
||||
{
|
||||
return obj switch
|
||||
{
|
||||
string _ => $"\"{obj}\"",
|
||||
null => "null",
|
||||
_ => $"<{obj}>"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNull{T}(T,string)"/> (where <typeparamref name="T"/> is <see langword="class"/>) fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNull<T>(T value, string name)
|
||||
where T : class
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be null, was {AssertString(value)} ({value.GetType().ToTypeString()})", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNull{T}(T,string)"/> (where <typeparamref name="T"/> is <see langword="struct"/>) fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNull<T>(T? value, string name)
|
||||
where T : struct
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T?).ToTypeString()}) must be null, was {AssertString(value)} ({typeof(T).ToTypeString()})", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentNullException"/> when <see cref="Guard.IsNotNull{T}(T,string)"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentNullExceptionForIsNotNull<T>(string name)
|
||||
{
|
||||
throw new ArgumentNullException(name, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be not null)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsOfType{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsOfType<T>(object value, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotOfType{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the input value.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotOfType<T>(object value, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must not be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsOfType"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsOfType(object value, Type type, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotOfType"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotOfType(object value, Type type, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must not be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsAssignableToType{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being checked against.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsAssignableToType<T>(object value, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotAssignableToType{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being checked against.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotAssignableToType<T>(object value, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must not be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsAssignableToType"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsAssignableToType(object value, Type type, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsAssignableToType"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotAssignableToType(object value, Type type, string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must not be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsReferenceEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input value being compared.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsReferenceEqualTo<T>(string name)
|
||||
where T : class
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the same instance as the target object", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsReferenceNotEqualTo{T}"/> fails.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of input value being compared.</typeparam>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsReferenceNotEqualTo<T>(string name)
|
||||
where T : class
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the same instance as the target object", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsTrue(bool,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsTrue(string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be true, was false", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsTrue(bool,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsTrue(string name, string message)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be true, was false: {AssertString(message)}", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsFalse(bool,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsFalse(string name)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be false, was true", name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsFalse(bool,string,string)"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsFalse(string name, string message)
|
||||
{
|
||||
throw new ArgumentException($"Parameter {AssertString(name)} must be false, was true: {AssertString(message)}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>Enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Title>Windows Community Toolkit - Diagnostics (.NET Standard)</Title>
|
||||
<Description>
|
||||
This package includes .NET Standard code only helpers such as:
|
||||
- Guard: Helper methods to verify conditions when running code.
|
||||
- ThrowHelper: Helper methods to efficiently throw exceptions.
|
||||
</Description>
|
||||
<PackageTags>Diagnostics;Guard;ThrowHelper;TypeInfo;Extensions;Helpers</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard1.4'">
|
||||
<!-- .NET Standard 1.4 doesn't have the Span<T> type, ValueTuple or the [Pure] attribute -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<!-- .NET Standard 2.0 doesn't have the Span<T> type -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PropertyGroup>
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PropertyGroup>
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Generated\Guard.Comparable.Numeric.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>Guard.Comparable.Numeric.g.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Generated\Guard.Collection.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>Guard.Collection.g.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Generated\ThrowHelper.Collection.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>ThrowHelper.Collection.g.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Generated\TypeInfo.ttinclude">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>TypeInfo.g.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- T4 service used by the Guard APIs -->
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Generated\Guard.Comparable.Numeric.g.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Guard.Comparable.Numeric.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Generated\Guard.Collection.g.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Guard.Collection.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Generated\ThrowHelper.Collection.g.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>ThrowHelper.Collection.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Generated\TypeInfo.g.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>TypeInfo.ttinclude</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,940 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
#if !NETSTANDARD1_4
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.Toolkit.Diagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArrayTypeMismatchException()
|
||||
{
|
||||
throw new ArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArrayTypeMismatchException(string? message)
|
||||
{
|
||||
throw new ArrayTypeMismatchException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArrayTypeMismatchException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArrayTypeMismatchException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new ArrayTypeMismatchException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException()
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArgumentException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException(string? message)
|
||||
{
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ArgumentException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new ArgumentException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArgumentException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException(string? name, string? message)
|
||||
{
|
||||
throw new ArgumentException(message, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ArgumentException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentException(string? name, string? message, Exception? innerException)
|
||||
{
|
||||
throw new ArgumentException(message, name, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentNullException()
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentNullException(string? name)
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentNullException(string? name, Exception? innerException)
|
||||
{
|
||||
throw new ArgumentNullException(name, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentNullException(string? name, string? message)
|
||||
{
|
||||
throw new ArgumentNullException(name, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException(string? name)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException(string? name, Exception? innerException)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException(string? name, string? message)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The argument name.</param>
|
||||
/// <param name="value">The current argument value.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentOutOfRangeException(string? name, object? value, string? message)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(name, value, message);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="COMException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="COMException">Thrown with no paarameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowCOMException()
|
||||
{
|
||||
throw new COMException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="COMException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="COMException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowCOMException(string? message)
|
||||
{
|
||||
throw new COMException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="COMException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The argument name.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="COMException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowCOMException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new COMException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="COMException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The argument name.</param>
|
||||
/// <param name="error">The HRESULT of the errror to include.</param>
|
||||
/// <exception cref="COMException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowCOMException(string? message, int error)
|
||||
{
|
||||
throw new COMException(message, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ExternalException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="ExternalException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowExternalException()
|
||||
{
|
||||
throw new ExternalException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ExternalException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ExternalException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowExternalException(string? message)
|
||||
{
|
||||
throw new ExternalException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ExternalException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The argument name.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ExternalException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowExternalException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new ExternalException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ExternalException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The argument name.</param>
|
||||
/// <param name="error">The HRESULT of the errror to include.</param>
|
||||
/// <exception cref="ExternalException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowExternalException(string? message, int error)
|
||||
{
|
||||
throw new ExternalException(message, error);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="FormatException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="FormatException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowFormatException()
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="FormatException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="FormatException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowFormatException(string? message)
|
||||
{
|
||||
throw new FormatException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="FormatException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="FormatException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowFormatException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new FormatException(message, innerException);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InsufficientMemoryException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="InsufficientMemoryException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInsufficientMemoryException()
|
||||
{
|
||||
throw new InsufficientMemoryException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InsufficientMemoryException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="InsufficientMemoryException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInsufficientMemoryException(string? message)
|
||||
{
|
||||
throw new InsufficientMemoryException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InsufficientMemoryException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="InsufficientMemoryException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInsufficientMemoryException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new InsufficientMemoryException(message, innerException);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidDataException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidDataException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidDataException()
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidDataException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="InvalidDataException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidDataException(string? message)
|
||||
{
|
||||
throw new InvalidDataException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidDataException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="InvalidDataException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidDataException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new InvalidDataException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidOperationException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidOperationException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidOperationException(string? message)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="InvalidOperationException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowInvalidOperationException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new InvalidOperationException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="LockRecursionException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="LockRecursionException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowLockRecursionException()
|
||||
{
|
||||
throw new LockRecursionException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="LockRecursionException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="LockRecursionException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowLockRecursionException(string? message)
|
||||
{
|
||||
throw new LockRecursionException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="LockRecursionException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="LockRecursionException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowLockRecursionException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new LockRecursionException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingFieldException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="MissingFieldException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingFieldException()
|
||||
{
|
||||
throw new MissingFieldException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingFieldException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="MissingFieldException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingFieldException(string? message)
|
||||
{
|
||||
throw new MissingFieldException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingFieldException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="MissingFieldException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingFieldException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new MissingFieldException(message, innerException);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingFieldException"/>.
|
||||
/// </summary>
|
||||
/// <param name="className">The target class being inspected.</param>
|
||||
/// <param name="fieldName">The target field being retrieved.</param>
|
||||
/// <exception cref="MissingFieldException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingFieldException(string? className, string? fieldName)
|
||||
{
|
||||
throw new MissingFieldException(className, fieldName);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMemberException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="MissingMemberException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMemberException()
|
||||
{
|
||||
throw new MissingMemberException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMemberException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="MissingMemberException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMemberException(string? message)
|
||||
{
|
||||
throw new MissingMemberException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMemberException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="MissingMemberException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMemberException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new MissingMemberException(message, innerException);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMemberException"/>.
|
||||
/// </summary>
|
||||
/// <param name="className">The target class being inspected.</param>
|
||||
/// <param name="memberName">The target member being retrieved.</param>
|
||||
/// <exception cref="MissingMemberException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMemberException(string? className, string? memberName)
|
||||
{
|
||||
throw new MissingMemberException(className, memberName);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMethodException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="MissingMethodException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMethodException()
|
||||
{
|
||||
throw new MissingMethodException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMethodException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="MissingMethodException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMethodException(string? message)
|
||||
{
|
||||
throw new MissingMethodException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMethodException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="MissingMethodException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMethodException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new MissingMethodException(message, innerException);
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="MissingMethodException"/>.
|
||||
/// </summary>
|
||||
/// <param name="className">The target class being inspected.</param>
|
||||
/// <param name="methodName">The target method being retrieved.</param>
|
||||
/// <exception cref="MissingMethodException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowMissingMethodException(string? className, string? methodName)
|
||||
{
|
||||
throw new MissingMethodException(className, methodName);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowNotSupportedException()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="NotSupportedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowNotSupportedException(string? message)
|
||||
{
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="NotSupportedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowNotSupportedException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new NotSupportedException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ObjectDisposedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="objectName">The name of the disposed object.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowObjectDisposedException(string? objectName)
|
||||
{
|
||||
throw new ObjectDisposedException(objectName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ObjectDisposedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="objectName">The name of the disposed object.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowObjectDisposedException(string? objectName, Exception? innerException)
|
||||
{
|
||||
throw new ObjectDisposedException(objectName, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="ObjectDisposedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="objectName">The name of the disposed object.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="ObjectDisposedException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowObjectDisposedException(string? objectName, string? message)
|
||||
{
|
||||
throw new ObjectDisposedException(objectName, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="OperationCanceledException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException()
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="OperationCanceledException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException(string? message)
|
||||
{
|
||||
throw new OperationCanceledException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="OperationCanceledException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new OperationCanceledException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <param name="token">The <see cref="CancellationToken"/> in use.</param>
|
||||
/// <exception cref="OperationCanceledException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException(CancellationToken token)
|
||||
{
|
||||
throw new OperationCanceledException(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="token">The <see cref="CancellationToken"/> in use.</param>
|
||||
/// <exception cref="OperationCanceledException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException(string? message, CancellationToken token)
|
||||
{
|
||||
throw new OperationCanceledException(message, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="OperationCanceledException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <param name="token">The <see cref="CancellationToken"/> in use.</param>
|
||||
/// <exception cref="OperationCanceledException">Thrown with the specified parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowOperationCanceledException(string? message, Exception? innerException, CancellationToken token)
|
||||
{
|
||||
throw new OperationCanceledException(message, innerException, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="PlatformNotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="PlatformNotSupportedException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowPlatformNotSupportedException()
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="PlatformNotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="PlatformNotSupportedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowPlatformNotSupportedException(string? message)
|
||||
{
|
||||
throw new PlatformNotSupportedException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="PlatformNotSupportedException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="PlatformNotSupportedException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowPlatformNotSupportedException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new PlatformNotSupportedException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="SynchronizationLockException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="SynchronizationLockException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowSynchronizationLockException()
|
||||
{
|
||||
throw new SynchronizationLockException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="SynchronizationLockException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="SynchronizationLockException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowSynchronizationLockException(string? message)
|
||||
{
|
||||
throw new SynchronizationLockException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="SynchronizationLockException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="SynchronizationLockException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowSynchronizationLockException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new SynchronizationLockException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="TimeoutException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="TimeoutException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowTimeoutException()
|
||||
{
|
||||
throw new TimeoutException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="TimeoutException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="TimeoutException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowTimeoutException(string? message)
|
||||
{
|
||||
throw new TimeoutException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="TimeoutException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="TimeoutException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowTimeoutException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new TimeoutException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="UnauthorizedAccessException"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="UnauthorizedAccessException">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowUnauthorizedAccessException()
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="UnauthorizedAccessException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="UnauthorizedAccessException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowUnauthorizedAccessException(string? message)
|
||||
{
|
||||
throw new UnauthorizedAccessException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="UnauthorizedAccessException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="UnauthorizedAccessException">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowUnauthorizedAccessException(string? message, Exception? innerException)
|
||||
{
|
||||
throw new UnauthorizedAccessException(message, innerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="Win32Exception"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="Win32Exception">Thrown with no parameters.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowWin32Exception()
|
||||
{
|
||||
throw new Win32Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="Win32Exception"/>.
|
||||
/// </summary>
|
||||
/// <param name="error">The Win32 error code associated with this exception.</param>
|
||||
/// <exception cref="Win32Exception">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowWin32Exception(int error)
|
||||
{
|
||||
throw new Win32Exception(error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="Win32Exception"/>.
|
||||
/// </summary>
|
||||
/// <param name="error">The Win32 error code associated with this exception.</param>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="Win32Exception">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowWin32Exception(int error, string? message)
|
||||
{
|
||||
throw new Win32Exception(error, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="Win32Exception"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <exception cref="Win32Exception">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowWin32Exception(string? message)
|
||||
{
|
||||
throw new Win32Exception(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new <see cref="Win32Exception"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to include in the exception.</param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> to include.</param>
|
||||
/// <exception cref="Win32Exception">Thrown with the specified parameter.</exception>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowWin32Exception(string? message, Exception? innerException)
|
||||
{
|
||||
throw new Win32Exception(message, innerException);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter
|
||||
/// will not be null even if the corresponding type allows it.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy of the .NET Standard 2.1 attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value,
|
||||
/// the associated parameter will not be <see langword="null"/>.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the return value should be <see langword="true"/>.
|
||||
/// </summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,28 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NET5_0
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate to the compiler that the <c>.locals init</c> flag should not be set in method headers.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy of the .NET 5 attribute.</remarks>
|
||||
[AttributeUsage(
|
||||
AttributeTargets.Module |
|
||||
AttributeTargets.Class |
|
||||
AttributeTargets.Struct |
|
||||
AttributeTargets.Interface |
|
||||
AttributeTargets.Constructor |
|
||||
AttributeTargets.Method |
|
||||
AttributeTargets.Property |
|
||||
AttributeTargets.Event,
|
||||
Inherited = false)]
|
||||
internal sealed class SkipLocalsInitAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,229 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="class"/> that represents a boxed <typeparamref name="T"/> value on the managed heap.
|
||||
/// This is a "shadow" type that can be used in place of a non-generic <see cref="object"/> reference to a
|
||||
/// boxed value type, to make the code more expressive and reduce the chances of errors.
|
||||
/// Consider this example:
|
||||
/// <code>
|
||||
/// object obj = 42;
|
||||
///
|
||||
/// // Manual, error prone unboxing
|
||||
/// int sum = (int)obj + 1;
|
||||
/// </code>
|
||||
/// In this example, it is not possible to know in advance what type is actually being boxed in a given
|
||||
/// <see cref="object"/> instance, making the code less robust at build time. The <see cref="Box{T}"/>
|
||||
/// type can be used as a drop-in replacement in this case, like so:
|
||||
/// <code>
|
||||
/// Box<int> box = 42;
|
||||
///
|
||||
/// // Build-time validation, automatic unboxing
|
||||
/// int sum = box.Value + 1;
|
||||
/// </code>
|
||||
/// This type can also be useful when dealing with large custom value types that are also boxed, as
|
||||
/// it allows to retrieve a mutable reference to the boxing value. This means that a given boxed
|
||||
/// value can be mutated in-place, instead of having to allocate a new updated boxed instance.
|
||||
/// </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>
|
||||
/// <exception cref="InvalidOperationException">Always thrown when this constructor is used (eg. from reflection).</exception>
|
||||
private Box()
|
||||
{
|
||||
throw new InvalidOperationException("The Microsoft.Toolkit.HighPerformance.Box<T> constructor should never be used");
|
||||
}
|
||||
|
||||
/// <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 responsibility 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)]
|
||||
public static bool TryGetFrom(object obj, [NotNullWhen(true)] 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 (T)(object)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>
|
||||
private static void ThrowInvalidCastExceptionForGetFrom()
|
||||
{
|
||||
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // Extensions being declared after the type they apply to
|
||||
#pragma warning disable SA1204 // Extension class to replace instance methods for Box<T>
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Box{T}"/> type.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,365 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
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(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default buffer size to use to expand empty arrays.
|
||||
/// </summary>
|
||||
private const int DefaultInitialBufferSize = 256;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
private T[]? array;
|
||||
|
||||
#pragma warning disable IDE0032 // Use field over auto-property (clearer and faster)
|
||||
/// <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()
|
||||
: this(ArrayPool<T>.Shared, DefaultInitialBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
public ArrayPoolBufferWriter(ArrayPool<T> pool)
|
||||
: this(pool, DefaultInitialBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
|
||||
public ArrayPoolBufferWriter(int initialCapacity)
|
||||
: this(ArrayPool<T>.Shared, initialCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
|
||||
public ArrayPoolBufferWriter(ArrayPool<T> pool, int initialCapacity)
|
||||
{
|
||||
// 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.
|
||||
// Additionally, we don't need to manually throw the exception if
|
||||
// the requested size is not valid, as that'll be thrown automatically
|
||||
// by the array pool in use when we try to rent an array with that size.
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(initialCapacity);
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
~ArrayPoolBufferWriter() => 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return array!.AsMemory(0, this.index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<T> WrittenSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return array!.AsSpan(0, this.index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int WrittenCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return array!.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FreeCapacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return array!.Length - this.index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
CheckBufferAndEnsureCapacity(sizeHint);
|
||||
|
||||
return this.array.AsMemory(this.index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Span<T> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
CheckBufferAndEnsureCapacity(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 CheckBufferAndEnsureCapacity(int sizeHint)
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
if (sizeHint < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
|
||||
}
|
||||
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
sizeHint = 1;
|
||||
}
|
||||
|
||||
if (sizeHint > array!.Length - this.index)
|
||||
{
|
||||
ResizeBuffer(sizeHint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes <see cref="array"/> to ensure it can fit the specified number of new items.
|
||||
/// </summary>
|
||||
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ResizeBuffer(int sizeHint)
|
||||
{
|
||||
int minimumSize = this.index + sizeHint;
|
||||
|
||||
// The ArrayPool<T> class has a maximum threshold of 1024 * 1024 for the maximum length of
|
||||
// pooled arrays, and once this is exceeded it will just allocate a new array every time
|
||||
// of exactly the requested size. In that case, we manually round up the requested size to
|
||||
// the nearest power of two, to ensure that repeated consecutive writes when the array in
|
||||
// use is bigger than that threshold don't end up causing a resize every single time.
|
||||
if (minimumSize > 1024 * 1024)
|
||||
{
|
||||
minimumSize = BitOperations.RoundUpPowerOfTwo(minimumSize);
|
||||
}
|
||||
|
||||
this.pool.Resize(ref this.array, minimumSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
this.array = null;
|
||||
|
||||
this.pool.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 requested count is negative.
|
||||
/// </summary>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException("The current buffer has already been disposed");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that expands <see cref="IBufferWriter{T}"/> with the ability to also inspect
|
||||
/// the written data, and to reset the underlying buffer to write again from the start.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current buffer.</typeparam>
|
||||
public interface IBuffer<T> : IBufferWriter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
|
||||
/// </summary>
|
||||
ReadOnlyMemory<T> WrittenMemory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </summary>
|
||||
ReadOnlySpan<T> WrittenSpan { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of data written to the underlying buffer so far.
|
||||
/// </summary>
|
||||
int WrittenCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total amount of space within the underlying buffer.
|
||||
/// </summary>
|
||||
int Capacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of space available that can still be written into without forcing the underlying buffer to grow.
|
||||
/// </summary>
|
||||
int FreeCapacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the data written to the underlying buffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You must clear the <see cref="IBuffer{T}"/> instance before trying to re-use it.
|
||||
/// </remarks>
|
||||
void Clear();
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <typeparamref name="TFrom"/> array, to <typeparamref name="TTo"/> values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
|
||||
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
|
||||
internal sealed class ArrayMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <typeparamref name="TFrom"/> array to read data from.
|
||||
/// </summary>
|
||||
private readonly TFrom[] array;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see name="array"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
|
||||
/// <summary>
|
||||
/// The original used length for <see name="array"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayMemoryManager{TFrom, TTo}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="array">The source <typeparamref name="TFrom"/> array to read data from.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="array"/>.</param>
|
||||
/// <param name="length">The original used length for <paramref name="array"/>.</param>
|
||||
public ArrayMemoryManager(TFrom[] array, int offset, int length)
|
||||
{
|
||||
this.array = array;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
|
||||
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
|
||||
int length = RuntimeHelpers.ConvertLength<TFrom, TTo>(this.length);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r1, length);
|
||||
#else
|
||||
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);
|
||||
|
||||
// We rely on MemoryMarshal.Cast here to deal with calculating the effective
|
||||
// size of the new span to return. This will also make the behavior consistent
|
||||
// for users that are both using this type as well as casting spans directly.
|
||||
return MemoryMarshal.Cast<TFrom, TTo>(span);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
|
||||
}
|
||||
|
||||
int
|
||||
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
|
||||
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
|
||||
byteOffset = bytePrefix + byteSuffix;
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);
|
||||
|
||||
ref TFrom r0 = ref this.array.DangerousGetReference();
|
||||
ref byte r1 = ref Unsafe.As<TFrom, byte>(ref r0);
|
||||
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
|
||||
void* pi = Unsafe.AsPointer(ref r2);
|
||||
|
||||
return new MemoryHandle(pi, handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unpin()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory<T>(int offset, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
// We need to calculate the right offset and length of the new Memory<T>. The local offset
|
||||
// is the original offset into the wrapped TFrom[] array, while the input offset is the one
|
||||
// with respect to TTo items in the Memory<TTo> instance that is currently being cast.
|
||||
int
|
||||
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
|
||||
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
|
||||
|
||||
// We have a special handling in cases where the user is circling back to the original type
|
||||
// of the wrapped array. In this case we can just return a memory wrapping that array directly,
|
||||
// with offset and length being adjusted, without the memory manager indirection.
|
||||
if (typeof(T) == typeof(TFrom))
|
||||
{
|
||||
return (Memory<T>)(object)this.array.AsMemory(absoluteOffset, absoluteLength);
|
||||
}
|
||||
|
||||
return new ArrayMemoryManager<TFrom, T>(this.array, absoluteOffset, absoluteLength).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
|
||||
/// </summary>
|
||||
internal interface IMemoryManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The target type to cast the items to.</typeparam>
|
||||
/// <param name="offset">The starting offset within the data store.</param>
|
||||
/// <param name="length">The original used length for the data store.</param>
|
||||
/// <returns>A new <see cref="Memory{T}"/> instance of the specified type, reinterpreting the current items.</returns>
|
||||
Memory<T> GetMemory<T>(int offset, int length)
|
||||
where T : unmanaged;
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="MemoryManager{T}"/> of <typeparamref name="TFrom"/>, to <typeparamref name="TTo"/> values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
|
||||
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
|
||||
internal sealed class ProxyMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="MemoryManager{T}"/> to read data from.
|
||||
/// </summary>
|
||||
private readonly MemoryManager<TFrom> memoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see name="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
|
||||
/// <summary>
|
||||
/// The original used length for <see name="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProxyMemoryManager{TFrom, TTo}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The source <see cref="MemoryManager{T}"/> to read data from.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="length">The original used length for <paramref name="memoryManager"/>.</param>
|
||||
public ProxyMemoryManager(MemoryManager<TFrom> memoryManager, int offset, int length)
|
||||
{
|
||||
this.memoryManager = memoryManager;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
Span<TFrom> span = this.memoryManager.GetSpan().Slice(this.offset, this.length);
|
||||
|
||||
return MemoryMarshal.Cast<TFrom, TTo>(span);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
|
||||
{
|
||||
ThrowArgumentExceptionForInvalidIndex();
|
||||
}
|
||||
|
||||
int
|
||||
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
|
||||
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
|
||||
byteOffset = bytePrefix + byteSuffix;
|
||||
|
||||
#if NETSTANDARD1_4
|
||||
int
|
||||
shiftedOffset = byteOffset / Unsafe.SizeOf<TFrom>(),
|
||||
remainder = byteOffset - (shiftedOffset * Unsafe.SizeOf<TFrom>());
|
||||
#else
|
||||
int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf<TFrom>(), out int remainder);
|
||||
#endif
|
||||
|
||||
if (remainder != 0)
|
||||
{
|
||||
ThrowArgumentExceptionForInvalidAlignment();
|
||||
}
|
||||
|
||||
return this.memoryManager.Pin(shiftedOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unpin()
|
||||
{
|
||||
this.memoryManager.Unpin();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
((IDisposable)this.memoryManager).Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory<T>(int offset, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
// Like in the other memory manager, calculate the absolute offset and length
|
||||
int
|
||||
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
|
||||
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
|
||||
|
||||
// Skip one indirection level and slice the original memory manager, if possible
|
||||
if (typeof(T) == typeof(TFrom))
|
||||
{
|
||||
return (Memory<T>)(object)this.memoryManager.Memory.Slice(absoluteOffset, absoluteLength);
|
||||
}
|
||||
|
||||
return new ProxyMemoryManager<TFrom, T>(this.memoryManager, absoluteOffset, absoluteLength).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForInvalidIndex()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Pin"/> receives an invalid target index.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForInvalidAlignment()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input index doesn't result in an aligned item access");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that can wrap arbitrary <see cref="object"/> instances.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the target memory area.</typeparam>
|
||||
internal sealed class RawObjectMemoryManager<T> : MemoryManager<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance.
|
||||
/// </summary>
|
||||
private readonly object instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the target memory area.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawObjectMemoryManager{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="instance"/>.</param>
|
||||
/// <param name="length">The usable length within <paramref name="instance"/>.</param>
|
||||
public RawObjectMemoryManager(object instance, IntPtr offset, int length)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Span<T> GetSpan()
|
||||
{
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)this.length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidElementIndex();
|
||||
}
|
||||
|
||||
// Allocating a pinned handle for the array with fail and throw an exception
|
||||
// if the array contains non blittable data. This is the expected behavior and
|
||||
// the same happens when trying to pin a Memory<T> instance obtained through
|
||||
// traditional means (eg. via the implicit T[] array conversion), if T is a
|
||||
// reference type or a type containing some references.
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex);
|
||||
void* p = Unsafe.AsPointer(ref r1);
|
||||
|
||||
return new MemoryHandle(p, handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unpin()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input index for <see cref="Pin"/> is not valid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,125 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="string"/> to <typeparamref name="TTo"/> values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTo">The target type to cast the source characters to.</typeparam>
|
||||
internal sealed class StringMemoryManager<TTo> : MemoryManager<TTo>, IMemoryManager
|
||||
where TTo : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="string"/> to read data from.
|
||||
/// </summary>
|
||||
private readonly string text;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see name="array"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
|
||||
/// <summary>
|
||||
/// The original used length for <see name="array"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringMemoryManager{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The source <see cref="string"/> to read data from.</param>
|
||||
/// <param name="offset">The starting offset within <paramref name="text"/>.</param>
|
||||
/// <param name="length">The original used length for <paramref name="text"/>.</param>
|
||||
public StringMemoryManager(string text, int offset, int length)
|
||||
{
|
||||
this.text = text;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref char r0 = ref this.text.DangerousGetReferenceAt(this.offset);
|
||||
ref TTo r1 = ref Unsafe.As<char, TTo>(ref r0);
|
||||
int length = RuntimeHelpers.ConvertLength<char, TTo>(this.length);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r1, length);
|
||||
#else
|
||||
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
|
||||
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
|
||||
|
||||
return MemoryMarshal.Cast<char, TTo>(span);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override unsafe MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<char>() / Unsafe.SizeOf<TTo>()))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
|
||||
}
|
||||
|
||||
int
|
||||
bytePrefix = this.offset * Unsafe.SizeOf<char>(),
|
||||
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
|
||||
byteOffset = bytePrefix + byteSuffix;
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.text, GCHandleType.Pinned);
|
||||
|
||||
ref char r0 = ref this.text.DangerousGetReference();
|
||||
ref byte r1 = ref Unsafe.As<char, byte>(ref r0);
|
||||
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
|
||||
void* pi = Unsafe.AsPointer(ref r2);
|
||||
|
||||
return new MemoryHandle(pi, handle);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Unpin()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory<T>(int offset, int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
int
|
||||
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, char>(offset),
|
||||
absoluteLength = RuntimeHelpers.ConvertLength<TTo, char>(length);
|
||||
|
||||
if (typeof(T) == typeof(char))
|
||||
{
|
||||
ReadOnlyMemory<char> memory = this.text.AsMemory(absoluteOffset, absoluteLength);
|
||||
|
||||
return (Memory<T>)(object)MemoryMarshal.AsMemory(memory);
|
||||
}
|
||||
|
||||
return new StringMemoryManager<T>(this.text, absoluteOffset, absoluteLength).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an output sink into which <typeparamref name="T"/> data can be written, backed by a <see cref="Memory{T}"/> instance.
|
||||
/// </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 wraps a <see cref="Memory{T}"/> instance.
|
||||
/// It can be used to bridge APIs consuming an <see cref="IBufferWriter{T}"/> with existing <see cref="Memory{T}"/>
|
||||
/// instances (or objects that can be converted to a <see cref="Memory{T}"/>), to ensure the data is written directly
|
||||
/// to the intended buffer, with no possibility of doing additional allocations or expanding the available capacity.
|
||||
/// </remarks>
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class MemoryBufferWriter<T> : IBuffer<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly Memory<T> memory;
|
||||
|
||||
#pragma warning disable IDE0032 // Use field over auto-property (like in ArrayPoolBufferWriter<T>)
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="memory"/>.
|
||||
/// </summary>
|
||||
private int index;
|
||||
#pragma warning restore IDE0032
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> instance to write to.</param>
|
||||
public MemoryBufferWriter(Memory<T> memory)
|
||||
{
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Slice(0, this.index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<T> WrittenSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Slice(0, this.index).Span;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int WrittenCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Capacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Length;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int FreeCapacity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.memory.Length - this.index;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Clear()
|
||||
{
|
||||
this.memory.Slice(0, this.index).Span.Clear();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Advance(int count)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeCount();
|
||||
}
|
||||
|
||||
if (this.index > this.memory.Length - count)
|
||||
{
|
||||
ThrowArgumentExceptionForAdvancedTooFar();
|
||||
}
|
||||
|
||||
this.index += count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Span<T> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index).Span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the requested size for either <see cref="GetMemory"/> or <see cref="GetSpan"/>.
|
||||
/// </summary>
|
||||
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="memory"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ValidateSizeHint(int sizeHint)
|
||||
{
|
||||
if (sizeHint < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
|
||||
}
|
||||
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
sizeHint = 1;
|
||||
}
|
||||
|
||||
if (sizeHint > FreeCapacity)
|
||||
{
|
||||
ThrowArgumentExceptionForCapacityExceeded();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public override string ToString()
|
||||
{
|
||||
// See comments in MemoryOwner<T> about this
|
||||
if (typeof(T) == typeof(char))
|
||||
{
|
||||
return this.memory.Slice(0, this.index).ToString();
|
||||
}
|
||||
|
||||
// Same representation used in Span<T>
|
||||
return $"Microsoft.Toolkit.HighPerformance.Buffers.MemoryBufferWriter<{typeof(T)}>[{this.index}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
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>
|
||||
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>
|
||||
private static void ThrowArgumentExceptionForAdvancedTooFar()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer has advanced too far");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForCapacityExceeded()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer doesn't have enough capacity left");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,355 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCORE_RUNTIME || NET5_0
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
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(MemoryDebugView<>))]
|
||||
[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 <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <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="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
private MemoryOwner(int length, ArrayPool<T> pool, AllocationMode mode)
|
||||
{
|
||||
this.start = 0;
|
||||
this.length = length;
|
||||
this.pool = pool;
|
||||
this.array = pool.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="start">The starting offset within <paramref name="array"/>.</param>
|
||||
/// <param name="length">The length of the array to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
|
||||
private MemoryOwner(int start, int length, ArrayPool<T> pool, T[] array)
|
||||
{
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
this.pool = pool;
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="MemoryOwner{T}"/> class.
|
||||
/// </summary>
|
||||
~MemoryOwner() => Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="MemoryOwner{T}"/> instance.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static MemoryOwner<T> Empty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new(0, ArrayPool<T>.Shared, 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(size, ArrayPool<T>.Shared, 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="pool">The <see cref="ArrayPool{T}"/> instance currently in 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, ArrayPool<T> pool) => new(size, pool, 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(size, ArrayPool<T>.Shared, mode);
|
||||
|
||||
/// <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="pool">The <see cref="ArrayPool{T}"/> instance currently in 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, ArrayPool<T> pool, AllocationMode mode) => new(size, pool, 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();
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME || NET5_0
|
||||
ref T r0 = ref array!.DangerousGetReferenceAt(this.start);
|
||||
|
||||
// On .NET Core runtimes, we can manually create a span from the starting reference to
|
||||
// skip the argument validations, which include an explicit null check, covariance check
|
||||
// for the array and the actual validation for the starting offset and target length. We
|
||||
// only do this on .NET Core as we can leverage the runtime-specific array layout to get
|
||||
// a fast access to the initial element, which makes this trick worth it. Otherwise, on
|
||||
// runtimes where we would need to at least access a static field to retrieve the base
|
||||
// byte offset within an SZ array object, we can get better performance by just using the
|
||||
// default Span<T> constructor and paying the cost of the extra conditional branches,
|
||||
// especially if T is a value type, in which case the covariance check is JIT removed.
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
#else
|
||||
return new Span<T>(array!, this.start, this.length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the buffer in use has already been disposed.</exception>
|
||||
/// <remarks>
|
||||
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
|
||||
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
|
||||
/// not used after the current <see cref="MemoryOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
|
||||
/// as the same array might be in use within another <see cref="MemoryOwner{T}"/> instance.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArraySegment<T> DangerousGetArray()
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return new ArraySegment<T>(array!, this.start, this.length);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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();
|
||||
}
|
||||
|
||||
// We're transferring the ownership of the underlying array, so the current
|
||||
// instance no longer needs to be disposed. Because of this, we can manually
|
||||
// suppress the finalizer to reduce the overhead on the garbage collector.
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
return new MemoryOwner<T>(start, length, this.pool, array!);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
this.array = null;
|
||||
|
||||
this.pool.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>
|
||||
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>
|
||||
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>
|
||||
private static void ThrowInvalidLengthException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCORE_RUNTIME || NET5_0
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
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(MemoryDebugView<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
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 <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <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="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
private SpanOwner(int length, ArrayPool<T> pool, AllocationMode mode)
|
||||
{
|
||||
this.length = length;
|
||||
this.pool = pool;
|
||||
this.array = pool.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(0, ArrayPool<T>.Shared, 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(size, ArrayPool<T>.Shared, 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="pool">The <see cref="ArrayPool{T}"/> instance 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, ArrayPool<T> pool) => new(size, pool, 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(size, ArrayPool<T>.Shared, mode);
|
||||
|
||||
/// <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="pool">The <see cref="ArrayPool{T}"/> instance 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, ArrayPool<T> pool, AllocationMode mode) => new(size, pool, 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
|
||||
{
|
||||
#if NETCORE_RUNTIME || NET5_0
|
||||
ref T r0 = ref array!.DangerousGetReference();
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
#else
|
||||
return new Span<T>(this.array, 0, this.length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 this.array.DangerousGetReference();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ArraySegment{T}"/> instance wrapping the underlying <typeparamref name="T"/> array in use.</returns>
|
||||
/// <remarks>
|
||||
/// This method is meant to be used when working with APIs that only accept an array as input, and should be used with caution.
|
||||
/// In particular, the returned array is rented from an array pool, and it is responsibility of the caller to ensure that it's
|
||||
/// not used after the current <see cref="SpanOwner{T}"/> instance is disposed. Doing so is considered undefined behavior,
|
||||
/// as the same array might be in use within another <see cref="SpanOwner{T}"/> instance.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArraySegment<T> DangerousGetArray()
|
||||
{
|
||||
return new(array!, 0, this.length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
this.pool.Return(this.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}]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,799 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
#if !NETSTANDARD1_4
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#endif
|
||||
using BitOperations = Microsoft.Toolkit.HighPerformance.Helpers.Internals.BitOperations;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// A configurable pool for <see cref="string"/> instances. This can be used to minimize allocations
|
||||
/// when creating multiple <see cref="string"/> instances from buffers of <see cref="char"/> values.
|
||||
/// The <see cref="GetOrAdd(ReadOnlySpan{char})"/> method provides a best-effort alternative to just creating
|
||||
/// a new <see cref="string"/> instance every time, in order to minimize the number of duplicated instances.
|
||||
/// The <see cref="StringPool"/> type will internally manage a highly efficient priority queue for the
|
||||
/// cached <see cref="string"/> instances, so that when the full capacity is reached, the least frequently
|
||||
/// used values will be automatically discarded to leave room for new values to cache.
|
||||
/// </summary>
|
||||
public sealed class StringPool
|
||||
{
|
||||
/// <summary>
|
||||
/// The size used by default by the parameterless constructor.
|
||||
/// </summary>
|
||||
private const int DefaultSize = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum size for <see cref="StringPool"/> instances.
|
||||
/// </summary>
|
||||
private const int MinimumSize = 32;
|
||||
|
||||
/// <summary>
|
||||
/// The current array of <see cref="FixedSizePriorityMap"/> instances in use.
|
||||
/// </summary>
|
||||
private readonly FixedSizePriorityMap[] maps;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of maps in use.
|
||||
/// </summary>
|
||||
private readonly int numberOfMaps;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringPool"/> class.
|
||||
/// </summary>
|
||||
public StringPool()
|
||||
: this(DefaultSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringPool"/> class.
|
||||
/// </summary>
|
||||
/// <param name="minimumSize">The minimum size for the pool to create.</param>
|
||||
public StringPool(int minimumSize)
|
||||
{
|
||||
if (minimumSize <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Set the minimum size
|
||||
minimumSize = Math.Max(minimumSize, MinimumSize);
|
||||
|
||||
// Calculates the rounded up factors for a specific size/factor pair
|
||||
static void FindFactors(int size, int factor, out int x, out int y)
|
||||
{
|
||||
double
|
||||
a = Math.Sqrt((double)size / factor),
|
||||
b = factor * a;
|
||||
|
||||
x = BitOperations.RoundUpPowerOfTwo((int)a);
|
||||
y = BitOperations.RoundUpPowerOfTwo((int)b);
|
||||
}
|
||||
|
||||
// We want to find two powers of 2 factors that produce a number
|
||||
// that is at least equal to the requested size. In order to find the
|
||||
// combination producing the optimal factors (with the product being as
|
||||
// close as possible to the requested size), we test a number of ratios
|
||||
// that we consider acceptable, and pick the best results produced.
|
||||
// The ratio between maps influences the number of objects being allocated,
|
||||
// as well as the multithreading performance when locking on maps.
|
||||
// We still want to contraint this number to avoid situations where we
|
||||
// have a way too high number of maps compared to total size.
|
||||
FindFactors(minimumSize, 2, out int x2, out int y2);
|
||||
FindFactors(minimumSize, 3, out int x3, out int y3);
|
||||
FindFactors(minimumSize, 4, out int x4, out int y4);
|
||||
|
||||
int
|
||||
p2 = x2 * y2,
|
||||
p3 = x3 * y3,
|
||||
p4 = x4 * y4;
|
||||
|
||||
if (p3 < p2)
|
||||
{
|
||||
p2 = p3;
|
||||
x2 = x3;
|
||||
y2 = y3;
|
||||
}
|
||||
|
||||
if (p4 < p2)
|
||||
{
|
||||
p2 = p4;
|
||||
x2 = x4;
|
||||
y2 = y4;
|
||||
}
|
||||
|
||||
Span<FixedSizePriorityMap> span = this.maps = new FixedSizePriorityMap[x2];
|
||||
|
||||
// We preallocate the maps in advance, since each bucket only contains the
|
||||
// array field, which is not preinitialized, so the allocations are minimal.
|
||||
// This lets us lock on each individual maps when retrieving a string instance.
|
||||
foreach (ref FixedSizePriorityMap map in span)
|
||||
{
|
||||
map = new FixedSizePriorityMap(y2);
|
||||
}
|
||||
|
||||
this.numberOfMaps = x2;
|
||||
|
||||
Size = p2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shared <see cref="StringPool"/> instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The shared pool provides a singleton, reusable <see cref="StringPool"/> instance that
|
||||
/// can be accessed directly, and that pools <see cref="string"/> instances for the entire
|
||||
/// process. Since <see cref="StringPool"/> is thread-safe, the shared instance can be used
|
||||
/// concurrently by multiple threads without the need for manual synchronization.
|
||||
/// </remarks>
|
||||
public static StringPool Shared { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of <see cref="string"/> that can be stored in the current instance.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores a <see cref="string"/> instance in the internal cache.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
|
||||
public void Add(string value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(value.AsSpan()),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
map.Add(value, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content, or stores the input one.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance with the contents to use.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>, cached if possible.</returns>
|
||||
public string GetOrAdd(string value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(value.AsSpan()),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.GetOrAdd(value, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content, or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
public string GetOrAdd(ReadOnlySpan<char> span)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(span),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.GetOrAdd(span, hashcode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached <see cref="string"/> instance matching the input content (converted to Unicode), or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use, in a specified encoding.</param>
|
||||
/// <param name="encoding">The <see cref="Encoding"/> instance to use to decode the contents of <paramref name="span"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
public unsafe string GetOrAdd(ReadOnlySpan<byte> span, Encoding encoding)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int maxLength = encoding.GetMaxCharCount(span.Length);
|
||||
|
||||
using SpanOwner<char> buffer = SpanOwner<char>.Allocate(maxLength);
|
||||
|
||||
fixed (byte* source = span)
|
||||
fixed (char* destination = &buffer.DangerousGetReference())
|
||||
{
|
||||
int effectiveLength = encoding.GetChars(source, span.Length, destination, maxLength);
|
||||
|
||||
return GetOrAdd(new ReadOnlySpan<char>(destination, effectiveLength));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a cached <see cref="string"/> instance matching the input content, if present.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
|
||||
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
|
||||
public bool TryGet(ReadOnlySpan<char> span, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
value = string.Empty;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
hashcode = GetHashCode(span),
|
||||
bucketIndex = hashcode & (this.numberOfMaps - 1);
|
||||
|
||||
ref FixedSizePriorityMap map = ref this.maps.DangerousGetReferenceAt(bucketIndex);
|
||||
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
return map.TryGet(span, hashcode, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current instance and its associated maps.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
foreach (ref FixedSizePriorityMap map in this.maps.AsSpan())
|
||||
{
|
||||
lock (map.SyncRoot)
|
||||
{
|
||||
map.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A configurable map containing a group of cached <see cref="string"/> instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Instances of this type are stored in an array within <see cref="StringPool"/> and they are
|
||||
/// always accessed by reference - essentially as if this type had been a class. The type is
|
||||
/// also private, so there's no risk for users to directly access it and accidentally copy an
|
||||
/// instance, which would lead to bugs due to values becoming out of sync with the internal state
|
||||
/// (that is, because instances would be copied by value, so primitive fields would not be shared).
|
||||
/// The reason why we're using a struct here is to remove an indirection level and improve cache
|
||||
/// locality when accessing individual buckets from the methods in the <see cref="StringPool"/> type.
|
||||
/// </remarks>
|
||||
private struct FixedSizePriorityMap
|
||||
{
|
||||
/// <summary>
|
||||
/// The index representing the end of a given list.
|
||||
/// </summary>
|
||||
private const int EndOfList = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The array of 1-based indices for the <see cref="MapEntry"/> items stored in <see cref="mapEntries"/>.
|
||||
/// </summary>
|
||||
private readonly int[] buckets;
|
||||
|
||||
/// <summary>
|
||||
/// The array of currently cached entries (ie. the lists for each hash group).
|
||||
/// </summary>
|
||||
private readonly MapEntry[] mapEntries;
|
||||
|
||||
/// <summary>
|
||||
/// The array of priority values associated to each item stored in <see cref="mapEntries"/>.
|
||||
/// </summary>
|
||||
private readonly HeapEntry[] heapEntries;
|
||||
|
||||
/// <summary>
|
||||
/// The current number of items stored in the map.
|
||||
/// </summary>
|
||||
private int count;
|
||||
|
||||
/// <summary>
|
||||
/// The current incremental timestamp for the items stored in <see cref="heapEntries"/>.
|
||||
/// </summary>
|
||||
private uint timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// A type representing a map entry, ie. a node in a given list.
|
||||
/// </summary>
|
||||
private struct MapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The precomputed hashcode for <see cref="Value"/>.
|
||||
/// </summary>
|
||||
public int HashCode;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="string"/> instance cached in this entry.
|
||||
/// </summary>
|
||||
public string? Value;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the next node in the current list.
|
||||
/// </summary>
|
||||
public int NextIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the heap entry corresponding to the current node.
|
||||
/// </summary>
|
||||
public int HeapIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type representing a heap entry, used to associate priority to each item.
|
||||
/// </summary>
|
||||
private struct HeapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The timestamp for the current entry (ie. the priority for the item).
|
||||
/// </summary>
|
||||
public uint Timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// The 0-based index for the map entry corresponding to the current item.
|
||||
/// </summary>
|
||||
public int MapIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FixedSizePriorityMap"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The fixed capacity of the current map.</param>
|
||||
public FixedSizePriorityMap(int capacity)
|
||||
{
|
||||
this.buckets = new int[capacity];
|
||||
this.mapEntries = new MapEntry[capacity];
|
||||
this.heapEntries = new HeapEntry[capacity];
|
||||
this.count = 0;
|
||||
this.timestamp = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="object"/> that can be used to synchronize access to the current instance.
|
||||
/// </summary>
|
||||
public object SyncRoot
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.buckets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.Add"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(string value, int hashcode)
|
||||
{
|
||||
ref string target = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (Unsafe.IsNullRef(ref target))
|
||||
{
|
||||
Insert(value, hashcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
target = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.GetOrAdd(string)"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="string"/> instance with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetOrAdd(string value, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(value.AsSpan(), hashcode);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Insert(value, hashcode);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.GetOrAdd(ReadOnlySpan{char})"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
string value = span.ToString();
|
||||
|
||||
Insert(value, hashcode);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="StringPool.TryGet"/> for the current instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
|
||||
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
ref string result = ref TryGet(span, hashcode);
|
||||
|
||||
if (!Unsafe.IsNullRef(ref result))
|
||||
{
|
||||
value = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current instance and throws away all the cached values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
this.buckets.AsSpan().Clear();
|
||||
this.mapEntries.AsSpan().Clear();
|
||||
this.heapEntries.AsSpan().Clear();
|
||||
this.count = 0;
|
||||
this.timestamp = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a target <see cref="string"/> instance, if it exists, and returns a reference to it.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> with the contents to use.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the slot where the target <see cref="string"/> instance could be.</returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref MapEntry entry = ref Unsafe.NullRef<MapEntry>();
|
||||
int
|
||||
length = this.buckets.Length,
|
||||
bucketIndex = hashcode & (length - 1);
|
||||
|
||||
for (int i = this.buckets.DangerousGetReferenceAt(bucketIndex) - 1;
|
||||
(uint)i < (uint)length;
|
||||
i = entry.NextIndex)
|
||||
{
|
||||
entry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)i);
|
||||
|
||||
if (entry.HashCode == hashcode &&
|
||||
entry.Value!.AsSpan().SequenceEqual(span))
|
||||
{
|
||||
UpdateTimestamp(ref entry.HeapIndex);
|
||||
|
||||
return ref entry.Value!;
|
||||
}
|
||||
}
|
||||
|
||||
return ref Unsafe.NullRef<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new <see cref="string"/> instance in the current map, freeing up a space if needed.
|
||||
/// </summary>
|
||||
/// <param name="value">The new <see cref="string"/> instance to store.</param>
|
||||
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Insert(string value, int hashcode)
|
||||
{
|
||||
ref int bucketsRef = ref this.buckets.DangerousGetReference();
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
int entryIndex, heapIndex;
|
||||
|
||||
// If the current map is full, first get the oldest value, which is
|
||||
// always the first item in the heap. Then, free up a slot by
|
||||
// removing that, and insert the new value in that empty location.
|
||||
if (this.count == this.mapEntries.Length)
|
||||
{
|
||||
entryIndex = heapEntriesRef.MapIndex;
|
||||
heapIndex = 0;
|
||||
|
||||
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
|
||||
// The removal logic can be extremely optimized in this case, as we
|
||||
// can retrieve the precomputed hashcode for the target entry by doing
|
||||
// a lookup on the target map node, and we can also skip all the comparisons
|
||||
// while traversing the target chain since we know in advance the index of
|
||||
// the target node which will contain the item to remove from the map.
|
||||
Remove(removedEntry.HashCode, entryIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the free list is not empty, get that map node and update the field
|
||||
entryIndex = this.count;
|
||||
heapIndex = this.count;
|
||||
}
|
||||
|
||||
int bucketIndex = hashcode & (this.buckets.Length - 1);
|
||||
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (nint)(uint)bucketIndex);
|
||||
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)heapIndex);
|
||||
|
||||
// Assign the values in the new map entry
|
||||
targetMapEntry.HashCode = hashcode;
|
||||
targetMapEntry.Value = value;
|
||||
targetMapEntry.NextIndex = targetBucket - 1;
|
||||
targetMapEntry.HeapIndex = heapIndex;
|
||||
|
||||
// Update the bucket slot and the current count
|
||||
targetBucket = entryIndex + 1;
|
||||
this.count++;
|
||||
|
||||
// Link the heap node with the current entry
|
||||
targetHeapEntry.MapIndex = entryIndex;
|
||||
|
||||
// Update the timestamp and balance the heap again
|
||||
UpdateTimestamp(ref targetMapEntry.HeapIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specified <see cref="string"/> instance from the map to free up one slot.
|
||||
/// </summary>
|
||||
/// <param name="hashcode">The precomputed hashcode of the instance to remove.</param>
|
||||
/// <param name="mapIndex">The index of the target map node to remove.</param>
|
||||
/// <remarks>The input <see cref="string"/> instance needs to already exist in the map.</remarks>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Remove(int hashcode, int mapIndex)
|
||||
{
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
int
|
||||
bucketIndex = hashcode & (this.buckets.Length - 1),
|
||||
entryIndex = this.buckets.DangerousGetReferenceAt(bucketIndex) - 1,
|
||||
lastIndex = EndOfList;
|
||||
|
||||
// We can just have an undefined loop, as the input
|
||||
// value we're looking for is guaranteed to be present
|
||||
while (true)
|
||||
{
|
||||
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
|
||||
|
||||
// Check the current value for a match
|
||||
if (entryIndex == mapIndex)
|
||||
{
|
||||
// If this was not the first list node, update the parent as well
|
||||
if (lastIndex != EndOfList)
|
||||
{
|
||||
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)lastIndex);
|
||||
|
||||
lastEntry.NextIndex = candidate.NextIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, update the target index from the bucket slot
|
||||
this.buckets.DangerousGetReferenceAt(bucketIndex) = candidate.NextIndex + 1;
|
||||
}
|
||||
|
||||
this.count--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the following node in the current list
|
||||
lastIndex = entryIndex;
|
||||
entryIndex = candidate.NextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timestamp of a heap node at the specified index (which is then synced back).
|
||||
/// </summary>
|
||||
/// <param name="heapIndex">The index of the target heap node to update.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void UpdateTimestamp(ref int heapIndex)
|
||||
{
|
||||
int
|
||||
currentIndex = heapIndex,
|
||||
count = this.count;
|
||||
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
|
||||
uint timestamp = this.timestamp;
|
||||
|
||||
// Check if incrementing the current timestamp for the heap node to update
|
||||
// would result in an overflow. If that happened, we could end up violating
|
||||
// the min-heap property (the value of each node has to always be <= than that
|
||||
// of its child nodes), eg. if we were updating a node that was not the root.
|
||||
// In that scenario, we could end up with a node somewhere along the heap with
|
||||
// a value lower than that of its parent node (as the timestamp would be 0).
|
||||
// To guard against this, we just check the current timestamp value, and if
|
||||
// the maximum value has been reached, we reinitialize the entire heap. This
|
||||
// is done in a non-inlined call, so we don't increase the codegen size in this
|
||||
// method. The reinitialization simply traverses the heap in breadth-first order
|
||||
// (ie. level by level), and assigns incrementing timestamps to all nodes starting
|
||||
// from 0. The value of the current timestamp is then just set to the current size.
|
||||
if (timestamp == uint.MaxValue)
|
||||
{
|
||||
// We use a goto here as this path is very rarely taken. Doing so
|
||||
// causes the generated asm to contain a forward jump to the fallback
|
||||
// path if this branch is taken, whereas the normal execution path will
|
||||
// not need to execute any jumps at all. This is done to reduce the overhead
|
||||
// introduced by this check in all the invocations where this point is not reached.
|
||||
goto Fallback;
|
||||
}
|
||||
|
||||
Downheap:
|
||||
|
||||
// Assign a new timestamp to the target heap node. We use a
|
||||
// local incremental timestamp instead of using the system timer
|
||||
// as this greatly reduces the overhead and the time spent in system calls.
|
||||
// The uint type provides a large enough range and it's unlikely users would ever
|
||||
// exhaust it anyway (especially considering each map has a separate counter).
|
||||
root.Timestamp = this.timestamp = timestamp + 1;
|
||||
|
||||
// Once the timestamp is updated (which will cause the heap to become
|
||||
// unbalanced), start a sift down loop to balance the heap again.
|
||||
while (true)
|
||||
{
|
||||
// The heap is 0-based (so that the array length can remain the same
|
||||
// as the power of 2 value used for the other arrays in this type).
|
||||
// This means that children of each node are at positions:
|
||||
// - left: (2 * n) + 1
|
||||
// - right: (2 * n) + 2
|
||||
ref HeapEntry minimum = ref root;
|
||||
int
|
||||
left = (currentIndex * 2) + 1,
|
||||
right = (currentIndex * 2) + 2,
|
||||
targetIndex = currentIndex;
|
||||
|
||||
// Check and update the left child, if necessary
|
||||
if (left < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)left);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
minimum = ref child;
|
||||
targetIndex = left;
|
||||
}
|
||||
}
|
||||
|
||||
// Same check as above for the right child
|
||||
if (right < count)
|
||||
{
|
||||
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)right);
|
||||
|
||||
if (child.Timestamp < minimum.Timestamp)
|
||||
{
|
||||
minimum = ref child;
|
||||
targetIndex = right;
|
||||
}
|
||||
}
|
||||
|
||||
// If no swap is pending, we can just stop here.
|
||||
// Before returning, we update the target index as well.
|
||||
if (Unsafe.AreSame(ref root, ref minimum))
|
||||
{
|
||||
heapIndex = targetIndex;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the indices in the respective map entries (accounting for the swap)
|
||||
Unsafe.Add(ref mapEntriesRef, (nint)(uint)root.MapIndex).HeapIndex = targetIndex;
|
||||
Unsafe.Add(ref mapEntriesRef, (nint)(uint)minimum.MapIndex).HeapIndex = currentIndex;
|
||||
|
||||
currentIndex = targetIndex;
|
||||
|
||||
// Swap the parent and child (so that the minimum value bubbles up)
|
||||
HeapEntry temp = root;
|
||||
|
||||
root = minimum;
|
||||
minimum = temp;
|
||||
|
||||
// Update the reference to the root node
|
||||
root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
|
||||
}
|
||||
|
||||
Fallback:
|
||||
|
||||
UpdateAllTimestamps();
|
||||
|
||||
// After having updated all the timestamps, if the heap contains N items, the
|
||||
// node in the bottom right corner will have a value of N - 1. Since the timestamp
|
||||
// is incremented by 1 before starting the downheap execution, here we simply
|
||||
// update the local timestamp to be N - 1, so that the code above will set the
|
||||
// timestamp of the node currently being updated to exactly N.
|
||||
timestamp = (uint)(count - 1);
|
||||
|
||||
goto Downheap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timestamp of all the current heap nodes in incrementing order.
|
||||
/// The heap is always guaranteed to be complete binary tree, so when it contains
|
||||
/// a given number of nodes, those are all contiguous from the start of the array.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void UpdateAllTimestamps()
|
||||
{
|
||||
int count = this.count;
|
||||
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Unsafe.Add(ref heapEntriesRef, (nint)(uint)i).Timestamp = (uint)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the (positive) hashcode for a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <returns>The hashcode for <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetHashCode(ReadOnlySpan<char> span)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return span.GetDjb2HashCode();
|
||||
#else
|
||||
return HashCode<char>.Combine(span);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minimumSize", "The requested size must be greater than 0");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items in a 1D layout.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to display.</typeparam>
|
||||
internal sealed class MemoryDebugView<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
|
||||
{
|
||||
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
|
||||
{
|
||||
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(MemoryOwner<T>? memoryOwner)
|
||||
{
|
||||
this.Items = memoryOwner?.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(SpanOwner<T> spanOwner)
|
||||
{
|
||||
this.Items = spanOwner.Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
|
||||
public T[]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,472 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates readonly items from arbitrary memory locations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
public readonly ref struct ReadOnlyRefEnumerable<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The total available length for the sequence.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The distance between items in the sequence to enumerate.
|
||||
/// </summary>
|
||||
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
|
||||
private readonly int step;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ReadOnlyRefEnumerable(ReadOnlySpan<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="reference">A reference to the first item of the sequence.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ReadOnlyRefEnumerable(in T reference, int length, int step)
|
||||
{
|
||||
this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length);
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="value">The reference to the first <typeparamref name="T"/> item to map.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> instance with the specified parameters.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the parameters are negative.</exception>
|
||||
[Pure]
|
||||
public static ReadOnlyRefEnumerable<T> DangerousCreate(in T value, int length, int step)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForLength();
|
||||
}
|
||||
|
||||
if (step < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForStep();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(length, 1, step);
|
||||
|
||||
return new ReadOnlyRefEnumerable<T>(in value, length, step);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total available length for the sequence.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
get => this.span.Length;
|
||||
#else
|
||||
get => this.length;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the specified zero-based index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element.</param>
|
||||
/// <returns>A reference to the element at the specified index.</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// Thrown when <paramref name="index"/> is invalid.
|
||||
/// </exception>
|
||||
public ref readonly T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint offset = (nint)(uint)index * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Gets the element at the specified zero-based index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element.</param>
|
||||
/// <returns>A reference to the element at the specified index.</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// Thrown when <paramref name="index"/> is invalid.
|
||||
/// </exception>
|
||||
public ref readonly T this[Index index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref this[index.GetOffset(Length)];
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new Enumerator(this.span, this.step);
|
||||
#else
|
||||
return new Enumerator(this.instance, this.offset, this.length, this.step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyRefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="ReadOnlyRefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.step == 1)
|
||||
{
|
||||
destination.CopyFrom(this.span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.Step == 1)
|
||||
{
|
||||
CopyTo(destination.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.span.DangerousGetReference();
|
||||
ref T destinationRef = ref destination.Span.DangerousGetReference();
|
||||
int
|
||||
sourceLength = this.span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step, (nint)(uint)destination.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyRefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int
|
||||
sourceLength = this.span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.step == 1)
|
||||
{
|
||||
this.span.CopyTo(destination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.span.DangerousGetReference();
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
int length = this.length;
|
||||
#endif
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
ref T destinationRef = ref destination.DangerousGetReference();
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
#endif
|
||||
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] array = new T[length];
|
||||
|
||||
CopyTo(array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="RefEnumerable{T}"/> instance into a <see cref="ReadOnlyRefEnumerable{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="enumerable">The input <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumerable)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Span, enumerable.Step);
|
||||
#else
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance with the info on the items to traverse.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(ReadOnlySpan<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "length" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForLength()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "step" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForStep()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("step");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using 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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct ReadOnlySpanEnumerable<T>
|
||||
{
|
||||
/// <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="ReadOnlySpanEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpanEnumerable(ReadOnlySpan<T> span)
|
||||
{
|
||||
this.span = span;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ReadOnlySpanEnumerable{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlySpanEnumerable<T> GetEnumerator() => this;
|
||||
|
||||
/// <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()
|
||||
{
|
||||
return ++this.index < this.span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly Item Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
// See comment in SpanEnumerable<T> about this
|
||||
return new Item(ref ri, this.index);
|
||||
#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="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <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.CreateReadOnlySpan(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="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="index">The current index within <paramref name="span"/>.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Item(ReadOnlySpan<T> span, int index)
|
||||
{
|
||||
this.span = span;
|
||||
this.index = index;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reference to the current value.
|
||||
/// </summary>
|
||||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public 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 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="ReadOnlySpanTokenizer{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="separator">The separator item to use.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator)
|
||||
{
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ReadOnlySpanTokenizer{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlySpanTokenizer<T> GetEnumerator() => this;
|
||||
|
||||
/// <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;
|
||||
|
||||
// We need to call this extension explicitly or the extension method resolution rules for the C# compiler
|
||||
// will end up picking Microsoft.Toolkit.HighPerformance.ReadOnlySpanExtensions.IndexOf instead, even
|
||||
// though the latter takes the parameter via a readonly reference. This is because the "in" modifier is
|
||||
// implicit, which makes the signature compatible, and because extension methods are matched in such a
|
||||
// way that methods "closest" to where they're used are preferred. Since this type shares the same root
|
||||
// namespace, this makes that extension a better match, so that it overrides the MemoryExtensions one.
|
||||
// This is not a problem for consumers of this package, as their code would be outside of the
|
||||
// Microsoft.Toolkit.HighPerformance namespace, so both extensions would be "equally distant", so that
|
||||
// when they're both in scope it will be possible to choose which one to use by adding an explicit "in".
|
||||
int index = System.MemoryExtensions.IndexOf(this.span.Slice(newEnd), 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 readonly ReadOnlySpan<T> Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.span.Slice(this.start, this.end - this.start);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,560 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that iterates items from arbitrary memory locations.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
public readonly ref struct RefEnumerable<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="Span{T}.Length"/> field maps to the total available length.</remarks>
|
||||
internal readonly Span<T> Span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
internal readonly object? Instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="Instance"/>.
|
||||
/// </summary>
|
||||
internal readonly IntPtr Offset;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The distance between items in the sequence to enumerate.
|
||||
/// </summary>
|
||||
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
|
||||
internal readonly int Step;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="reference">A reference to the first item of the sequence.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal RefEnumerable(ref T reference, int length, int step)
|
||||
{
|
||||
Span = MemoryMarshal.CreateSpan(ref reference, length);
|
||||
Step = step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RefEnumerable{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="value">The reference to the first <typeparamref name="T"/> item to map.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> instance with the specified parameters.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the parameters are negative.</exception>
|
||||
[Pure]
|
||||
public static RefEnumerable<T> DangerousCreate(ref T value, int length, int step)
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForLength();
|
||||
}
|
||||
|
||||
if (step < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForStep();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(length, 1, step);
|
||||
|
||||
return new RefEnumerable<T>(ref value, length, step);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
Instance = instance;
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
Step = step;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total available length for the sequence.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
get => this.Span.Length;
|
||||
#else
|
||||
get;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the specified zero-based index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element.</param>
|
||||
/// <returns>A reference to the element at the specified index.</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// Thrown when <paramref name="index"/> is invalid.
|
||||
/// </exception>
|
||||
public ref T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.Span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
#endif
|
||||
nint offset = (nint)(uint)index * (nint)(uint)this.Step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Gets the element at the specified zero-based index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element.</param>
|
||||
/// <returns>A reference to the element at the specified index.</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">
|
||||
/// Thrown when <paramref name="index"/> is invalid.
|
||||
/// </exception>
|
||||
public ref T this[Index index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref this[index.GetOffset(Length)];
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new Enumerator(this.Span, this.Step);
|
||||
#else
|
||||
return new Enumerator(this.Instance, this.Offset, this.Length, this.Step);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the contents of the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
// Fast path for contiguous items
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T r0 = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
destination.CopyFrom(this.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.Step == 1)
|
||||
{
|
||||
CopyTo(destination.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.Span.DangerousGetReference();
|
||||
ref T destinationRef = ref destination.Span.DangerousGetReference();
|
||||
int
|
||||
sourceLength = this.Span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(RefEnumerable<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int
|
||||
sourceLength = this.Span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.CopyTo(destination);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T sourceRef = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
ref T destinationRef = ref destination.DangerousGetReference();
|
||||
|
||||
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the current <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </exception>
|
||||
internal void CopyFrom(ReadOnlySpan<T> source)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
source.CopyTo(this.Span);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T destinationRef = ref this.Span.DangerousGetReference();
|
||||
int destinationLength = this.Span.Length;
|
||||
#else
|
||||
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int destinationLength = this.Length;
|
||||
#endif
|
||||
ref T sourceRef = ref source.DangerousGetReference();
|
||||
int sourceLength = source.Length;
|
||||
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyFrom(ReadOnlySpan<T> source)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (length >= source.Length)
|
||||
{
|
||||
CopyFrom(source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of this <see cref="RefEnumerable{T}"/> with a specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to assign to each element of the <see cref="RefEnumerable{T}"/> instance.</param>
|
||||
public void Fill(T value)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.Step == 1)
|
||||
{
|
||||
this.Span.Fill(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ref T r0 = ref this.Span.DangerousGetReference();
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <typeparamref name="T"/> array with the values in the target row.
|
||||
/// </summary>
|
||||
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
|
||||
/// <remarks>
|
||||
/// This method will allocate a new <typeparamref name="T"/> array, so only
|
||||
/// use it if you really need to copy the target items in a new memory location.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
T[] array = new T[length];
|
||||
|
||||
CopyTo(array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Span"/>
|
||||
private readonly Span<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="Span{T}"/> instance with the info on the items to traverse.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(Span<T> span, int step)
|
||||
{
|
||||
this.span = span;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
|
||||
/// <param name="length">The number of items in the sequence.</param>
|
||||
/// <param name="step">The distance between items in the sequence to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(object? instance, IntPtr offset, int length, int step)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
this.step = step;
|
||||
this.position = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
|
||||
// Here we just offset by shifting down as if we were traversing a 2D array with a
|
||||
// a single column, with the width of each row represented by the step, the height
|
||||
// represented by the current position, and with only the first element of each row
|
||||
// being inspected. We can perform all the indexing operations in this type as nint,
|
||||
// as the maximum offset is guaranteed never to exceed the maximum value, since on
|
||||
// 32 bit architectures it's not possible to allocate that much memory anyway.
|
||||
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "length" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForLength()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "step" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForStep()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("step");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using 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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct SpanEnumerable<T>
|
||||
{
|
||||
/// <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="SpanEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanEnumerable(Span<T> span)
|
||||
{
|
||||
this.span = span;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="SpanEnumerable{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly SpanEnumerable<T> GetEnumerator() => this;
|
||||
|
||||
/// <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()
|
||||
{
|
||||
return ++this.index < this.span.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly Item Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
|
||||
// support for the Span<T> types), we can save 4 bytes by piggybacking the
|
||||
// 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 eg. .NET Standard 2.0,
|
||||
// as we lack the API to create Span<T>-s 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 SPAN_RUNTIME_SUPPORT
|
||||
/// <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 SPAN_RUNTIME_SUPPORT
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public 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 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="SpanTokenizer{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="separator">The separator item to use.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanTokenizer(Span<T> span, T separator)
|
||||
{
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="SpanTokenizer{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly SpanTokenizer<T> GetEnumerator() => this;
|
||||
|
||||
/// <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 readonly Span<T> Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.span.Slice(this.start, this.end - this.start);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCORE_RUNTIME || NET5_0
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
#if !NETCORE_RUNTIME && !NET5_0
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
#if NET5_0
|
||||
return ref MemoryMarshal.GetArrayDataReference(array);
|
||||
#elif NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
#if NET5_0
|
||||
ref T r0 = ref MemoryMarshal.GetArrayDataReference(array);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
#elif NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArrayData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// 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
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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(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(array, separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <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
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="OverflowException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowOverflowException()
|
||||
{
|
||||
throw new OverflowException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,467 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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 responsibility 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)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray2DData>(array)!;
|
||||
nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
int width = array.GetLength(1);
|
||||
nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j;
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// 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
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="RefEnumerable{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RefEnumerable<T> GetRow<T>(this T[,] array, int row)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
|
||||
if ((uint)row >= (uint)height)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
|
||||
return new RefEnumerable<T>(ref r0, width, 1);
|
||||
#else
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="RefEnumerable{T}"/> that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// This extension should be used directly within a <see langword="foreach"/> loop:
|
||||
/// <code>
|
||||
/// int[,] matrix =
|
||||
/// {
|
||||
/// { 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="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the input parameters is out of range.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RefEnumerable<T> GetColumn<T>(this T[,] array, int column)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
|
||||
if ((uint)column >= (uint)width)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
|
||||
|
||||
return new RefEnumerable<T>(ref r0, height, width);
|
||||
#else
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, height, width);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,]? array)
|
||||
{
|
||||
return new(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
return new(array, row, column, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,]? array)
|
||||
{
|
||||
return new(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
return new(array, row, column, height, width);
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> GetRowSpan<T>(this T[,] array, int row)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="row">The target row to retrieve (0-based index).</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> GetRowMemory<T>(this T[,] array, int row)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)row >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, array.GetLength(1)).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
int length = array.Length;
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span{T}"/> over an input 2D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int length = array.Length;
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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 unsafe int Count<T>(this T[,] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input 2D <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 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 unsafe int GetDjb2HashCode<T>(this T[,] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[,] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[,]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
private static void ThrowArrayTypeMismatchException()
|
||||
{
|
||||
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("row");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("column");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,324 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within a given 3D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A reference to the first element within <paramref name="array"/>, or the location it would have used, if <paramref name="array"/> is empty.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReference<T>(this T[,,] array)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array)!;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
|
||||
return ref r0;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified coordinate within a given 3D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="i">The depth index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <param name="j">The vertical index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <param name="k">The horizontal index of the element to retrieve within <paramref name="array"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="array"/> at the coordinate specified by <paramref name="i"/> and <paramref name="j"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/>
|
||||
/// and <paramref name="j"/> parameters are valid. Furthermore, this extension will ignore the lower bounds for the input
|
||||
/// array, and will just assume that the input index is 0-based. It is responsibility of the caller to adjust the input
|
||||
/// indices to account for the actual lower bounds, if the input array has either axis not starting at 0.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k)
|
||||
{
|
||||
#if NETCORE_RUNTIME
|
||||
var arrayData = Unsafe.As<RawArray3DData>(array)!;
|
||||
nint offset =
|
||||
((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
|
||||
((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
|
||||
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
#else
|
||||
int
|
||||
height = array.GetLength(1),
|
||||
width = array.GetLength(2);
|
||||
nint index =
|
||||
((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
|
||||
((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
ref T ri = ref Unsafe.Add(ref r0, index);
|
||||
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETCORE_RUNTIME
|
||||
// See description for this in the 2D partial file.
|
||||
// Using the CHW naming scheme here (like with RGB images).
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private sealed class RawArray3DData
|
||||
{
|
||||
#pragma warning disable CS0649 // Unassigned fields
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public IntPtr Length;
|
||||
public int Channel;
|
||||
public int Height;
|
||||
public int Width;
|
||||
public int ChannelLowerBound;
|
||||
public int HeightLowerBound;
|
||||
public int WidthLowerBound;
|
||||
public byte Data;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory{T}"/> over an input 3D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
int length = array.Length;
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span{T}"/> over an input 3D <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
int length = array.Length;
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Span{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Span{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
|
||||
int length = checked(array.GetLength(1) * array.GetLength(2));
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Memory{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Memory{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<T> AsMemory<T>(this T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
int length = checked(array.GetLength(1) * array.GetLength(2));
|
||||
|
||||
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Span2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Span2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth)
|
||||
{
|
||||
return new(array, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this T[,,] array, int depth)
|
||||
{
|
||||
return new(array, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of occurrences of a given value into a target 3D <typeparamref name="T"/> array instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this T[,,] array, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint
|
||||
length = RuntimeHelpers.GetArrayNativeLength(array),
|
||||
count = SpanHelper.Count(ref r0, length, value);
|
||||
|
||||
if ((nuint)count > int.MaxValue)
|
||||
{
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input 3D <typeparamref name="T"/> array instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input 3D <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>The Djb2 value for the input 3D <typeparamref name="T"/> array instance.</returns>
|
||||
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this T[,,] array)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref array.DangerousGetReference();
|
||||
nint length = RuntimeHelpers.GetArrayNativeLength(array);
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given <typeparamref name="T"/> array is covariant.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
|
||||
/// <returns>Whether or not <paramref name="array"/> is covariant.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCovariant<T>(this T[,,] array)
|
||||
{
|
||||
return default(T) is null && array.GetType() != typeof(T[,,]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForDepth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("depth");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.Toolkit.HighPerformance.Streams;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ArrayPoolBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ArrayPoolBufferWriterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="ArrayPoolBufferWriter{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="writer">The target <see cref="ArrayPoolBufferWriter{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
|
||||
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this ArrayPoolBufferWriter<byte> writer)
|
||||
{
|
||||
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(writer));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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.Copy(array, 0, newArray, 0, itemsToCopy);
|
||||
|
||||
pool.Return(array, clearArray);
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="bool"/> type.
|
||||
/// </summary>
|
||||
public static class BoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>1 if <paramref name="flag"/> is <see langword="true"/>, 0 otherwise.</returns>
|
||||
/// <remarks>This method does not contain branching instructions.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe byte ToByte(this bool flag)
|
||||
{
|
||||
// Whenever we need to take the address of an argument, we make a local copy first.
|
||||
// This will be removed by the JIT anyway, but it can help produce better codegen and
|
||||
// remove unwanted stack spills if the caller is using constant arguments. This is
|
||||
// because taking the address of an argument can interfere with some of the flow
|
||||
// analysis executed by the JIT, which can in some cases block constant propagation.
|
||||
bool copy = flag;
|
||||
|
||||
return *(byte*)©
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value to an <see cref="int"/> mask with
|
||||
/// all bits representing the value of the input flag (either 0xFFFFFFFF or 0x00000000).
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>0xFFFFFFFF if <paramref name="flag"/> is <see langword="true"/>, 0x00000000 otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// This method does not contain branching instructions, and it is only guaranteed to work with
|
||||
/// <see cref="bool"/> values being either 0 or 1. Operations producing a <see cref="bool"/> result,
|
||||
/// such as numerical comparisons, always result in a valid value. If the <see cref="bool"/> value is
|
||||
/// produced by fields with a custom <see cref="System.Runtime.InteropServices.FieldOffsetAttribute"/>,
|
||||
/// or by using <see cref="Unsafe.As{T}"/> or other unsafe APIs to directly manipulate the underlying
|
||||
/// data though, it is responsibility of the caller to ensure the validity of the provided value.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int ToBitwiseMask32(this bool flag)
|
||||
{
|
||||
bool copy = flag;
|
||||
byte rangeFlag = *(byte*)©
|
||||
int
|
||||
negativeFlag = rangeFlag - 1,
|
||||
mask = ~negativeFlag;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given <see cref="bool"/> value to a <see cref="long"/> mask with
|
||||
/// all bits representing the value of the input flag (either all 1s or 0s).
|
||||
/// </summary>
|
||||
/// <param name="flag">The input value to convert.</param>
|
||||
/// <returns>All 1s if <paramref name="flag"/> is <see langword="true"/>, all 0s otherwise.</returns>
|
||||
/// <remarks>This method does not contain branching instructions. See additional note in <see cref="ToBitwiseMask32"/>.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe long ToBitwiseMask64(this bool flag)
|
||||
{
|
||||
bool copy = flag;
|
||||
byte rangeFlag = *(byte*)©
|
||||
long
|
||||
negativeFlag = (long)rangeFlag - 1,
|
||||
mask = ~negativeFlag;
|
||||
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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)
|
||||
where T : notnull
|
||||
{
|
||||
int hash = HashCode<T>.CombineValues(span);
|
||||
|
||||
hashCode.Add(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,136 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.Toolkit.HighPerformance.Streams;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
public static class IBufferWriterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="IBufferWriter{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
|
||||
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this IBufferWriter<byte> writer)
|
||||
{
|
||||
if (writer.GetType() == typeof(ArrayPoolBufferWriter<byte>))
|
||||
{
|
||||
// If the input writer is of type ArrayPoolBufferWriter<byte>, we can use the type
|
||||
// specific buffer writer owner to let the JIT elide callvirts when accessing it.
|
||||
var internalWriter = Unsafe.As<ArrayPoolBufferWriter<byte>>(writer)!;
|
||||
|
||||
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(internalWriter));
|
||||
}
|
||||
|
||||
return new IBufferWriterStream<IBufferWriterOwner>(new IBufferWriterOwner(writer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
public static void Write<T>(this IBufferWriter<byte> writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
Span<byte> span = writer.GetSpan(1);
|
||||
|
||||
if (span.Length < length)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
ref byte r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
Unsafe.WriteUnaligned(ref r0, value);
|
||||
|
||||
writer.Advance(length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<T> writer, T value)
|
||||
{
|
||||
Span<T> span = writer.GetSpan(1);
|
||||
|
||||
if (span.Length < 1)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
MemoryMarshal.GetReference(span) = value;
|
||||
|
||||
writer.Advance(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a series of items of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<byte> writer, ReadOnlySpan<T> span)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> source = MemoryMarshal.AsBytes(span);
|
||||
Span<byte> destination = writer.GetSpan(source.Length);
|
||||
|
||||
source.CopyTo(destination);
|
||||
|
||||
writer.Advance(source.Length);
|
||||
}
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Writes a series of items of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance to write to.</param>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to write to <paramref name="writer"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="writer"/> reaches the end.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Write<T>(this IBufferWriter<T> writer, ReadOnlySpan<T> span)
|
||||
{
|
||||
Span<T> destination = writer.GetSpan(span.Length);
|
||||
|
||||
span.CopyTo(destination);
|
||||
|
||||
writer.Advance(span.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target writer.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForEndOfBuffer()
|
||||
{
|
||||
throw new ArgumentException("The current buffer writer can't contain the requested input data.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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="memoryOwner">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
|
||||
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memoryOwner"/>.</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>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memoryOwner"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this IMemoryOwner<byte> memoryOwner)
|
||||
{
|
||||
return MemoryStream.Create(memoryOwner);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if NET5_0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="List{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ListExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Span{T}"/> over an input <see cref="List{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="List{T}"/> instance.</typeparam>
|
||||
/// <param name="list">The input <see cref="List{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="list"/>.</returns>
|
||||
/// <remarks>
|
||||
/// Note that the returned <see cref="Span{T}"/> is only guaranteed to be valid as long as the items within
|
||||
/// <paramref name="list"/> are not modified. Doing so might cause the <see cref="List{T}"/> to swap its
|
||||
/// internal buffer, causing the returned <see cref="Span{T}"/> to become out of date. That means that in this
|
||||
/// scenario, the <see cref="Span{T}"/> would end up wrapping an array no longer in use. Always make sure to use
|
||||
/// the returned <see cref="Span{T}"/> while the target <see cref="List{T}"/> is not modified.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<T> AsSpan<T>(this List<T>? list)
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,119 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Memory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class MemoryExtensions
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width)
|
||||
{
|
||||
return new(memory, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new(memory, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Memory{T}"/> of bytes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="Memory{T}"/>.</typeparam>
|
||||
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="T"/>.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> of bytes.</returns>
|
||||
/// <exception cref="OverflowException">
|
||||
/// Thrown if the <see cref="Memory{T}.Length"/> property of the new <see cref="Memory{T}"/> would exceed <see cref="int.MaxValue"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<byte> AsBytes<T>(this Memory<T> memory)
|
||||
where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.AsMemory(((ReadOnlyMemory<T>)memory).Cast<T, byte>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">The type of items in the source <see cref="Memory{T}"/>.</typeparam>
|
||||
/// <typeparam name="TTo">The type of items in the destination <see cref="Memory{T}"/>.</typeparam>
|
||||
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
|
||||
/// <returns>A <see cref="Memory{T}"/> of type <typeparamref name="TTo"/></returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory)
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
return MemoryMarshal.AsMemory(((ReadOnlyMemory<TFrom>)memory).Cast<TFrom, TTo>());
|
||||
}
|
||||
|
||||
/// <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 responsibility 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>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this Memory<byte> memory)
|
||||
{
|
||||
return MemoryStream.Create(memory, false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// This extension is restricted to the .NET 5 because it shares the same BCL
|
||||
// across all targets, ensuring that the layout of our Nullable<T> mapping type
|
||||
// will be correct. Exposing this API on older targets (especially .NET Standard)
|
||||
// is not guaranteed to be correct and could result in invalid memory accesses.
|
||||
#if NET5_0
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Nullable{T}"/> type.
|
||||
/// </summary>
|
||||
public static class NullableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a reference to the value of the input <see cref="Nullable{T}"/> instance, regardless of whether
|
||||
/// the <see cref="Nullable{T}.HasValue"/> property is returning <see langword="true"/> or not. If that is not
|
||||
/// the case, this method will still return a reference to the underlying <see langword="default"/> value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the underlying value</typeparam>
|
||||
/// <param name="value">The <see cref="Nullable{T}"/></param>
|
||||
/// <returns>A reference to the underlying value from the input <see cref="Nullable{T}"/> instance.</returns>
|
||||
/// <remarks>
|
||||
/// Note that attempting to mutate the returned reference will not change the value returned by <see cref="Nullable{T}.HasValue"/>.
|
||||
/// That means that reassigning the value of an empty instance will not make <see cref="Nullable{T}.HasValue"/> return <see langword="true"/>.
|
||||
/// </remarks>
|
||||
public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
|
||||
where T : struct
|
||||
{
|
||||
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mapping type that reflects the internal layout of the <see cref="Nullable{T}"/> type.
|
||||
/// See https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Nullable.cs.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type wrapped by the current instance.</typeparam>
|
||||
private struct RawNullableData<T>
|
||||
where T : struct
|
||||
{
|
||||
#pragma warning disable CS0649 // Unassigned fields
|
||||
public bool HasValue;
|
||||
public T Value;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,158 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlyMemory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ReadOnlyMemoryExtensions
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width)
|
||||
{
|
||||
return new(memory, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new(memory, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlyMemory{T}"/> of bytes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
|
||||
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="T"/>.</param>
|
||||
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of bytes.</returns>
|
||||
/// <exception cref="OverflowException">
|
||||
/// Thrown if the <see cref="ReadOnlyMemory{T}.Length"/> property of the new <see cref="ReadOnlyMemory{T}"/> would exceed <see cref="int.MaxValue"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory)
|
||||
where T : unmanaged
|
||||
{
|
||||
return Cast<T, byte>(memory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
|
||||
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlyMemory{T}"/>.</typeparam>
|
||||
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
|
||||
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of type <typeparamref name="TTo"/></returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory)
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (typeof(TFrom) == typeof(char) &&
|
||||
MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string? text, out int start, out int length))
|
||||
{
|
||||
return new StringMemoryManager<TTo>(text!, start, length).Memory;
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<TFrom> segment))
|
||||
{
|
||||
return new ArrayMemoryManager<TFrom, TTo>(segment.Array!, segment.Offset, segment.Count).Memory;
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(memory, out var memoryManager, out start, out length))
|
||||
{
|
||||
// If the memory manager is the one resulting from a previous cast, we can use it directly to retrieve
|
||||
// a new manager for the target type that wraps the original data store, instead of creating one that
|
||||
// wraps the current manager. This ensures that doing repeated casts always results in only up to one
|
||||
// indirection level in the chain of memory managers needed to access the target data buffer to use.
|
||||
if (memoryManager is IMemoryManager wrappingManager)
|
||||
{
|
||||
return wrappingManager.GetMemory<TTo>(start, length);
|
||||
}
|
||||
|
||||
return new ProxyMemoryManager<TFrom, TTo>(memoryManager, start, length).Memory;
|
||||
}
|
||||
|
||||
// Throws when the memory instance has an unsupported backing store
|
||||
static ReadOnlyMemory<TTo> ThrowArgumentExceptionForUnsupportedMemory()
|
||||
{
|
||||
throw new ArgumentException("The input instance doesn't have a supported underlying data store.");
|
||||
}
|
||||
|
||||
return ThrowArgumentExceptionForUnsupportedMemory();
|
||||
}
|
||||
|
||||
/// <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 responsibility 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>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
return MemoryStream.Create(memory, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,403 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
|
||||
/// </summary>
|
||||
public static 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)
|
||||
{
|
||||
// Here we assume the input index will never be negative, so we do a (nint)(uint) cast
|
||||
// to force the JIT to skip the sign extension when going from int to native int.
|
||||
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
// L0003: movsxd rdx, edx
|
||||
// L0006: lea rax, [rax+rdx*4]
|
||||
// L000a: ret
|
||||
// =============================
|
||||
// Note the movsxd (move with sign extension) to expand the index passed in edx to
|
||||
// the whole rdx register. This is unnecessary and more expensive than just a mov,
|
||||
// which when done to a large register size automatically zeroes the upper bits.
|
||||
// With the (nint)(uint) cast, we get the following codegen instead:
|
||||
// =============================
|
||||
// L0000: mov rax, [rcx]
|
||||
// L0003: mov edx, edx
|
||||
// L0005: lea rax, [rax+rdx*4]
|
||||
// L0009: ret
|
||||
// =============================
|
||||
// Here we can see how the index is extended to a native integer with just a mov,
|
||||
// which effectively only zeroes the upper bits of the same register used as source.
|
||||
// These three casts are a bit verbose, but they do the trick on both 32 bit and 64
|
||||
// bit architectures, producing optimal code in both cases (they are either completely
|
||||
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
|
||||
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
|
||||
// then cast to nint, so that we can obtain an IntPtr value without the range check (since
|
||||
// uint could be out of range there if the original index was negative). The final result
|
||||
// is a clean mov as shown above. This will eventually be natively supported by the JIT
|
||||
// compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
|
||||
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified index within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, nint i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within a given <see cref="ReadOnlySpan{T}"/>, clamping the input index in the valid range.
|
||||
/// If the <paramref name="i"/> parameter exceeds the length of <paramref name="span"/>, it will be clamped to 0.
|
||||
/// Therefore, the returned reference will always point to a valid element within <paramref name="span"/>, assuming it is not empty.
|
||||
/// This method is specifically meant to efficiently index lookup tables, especially if they point to constant data.
|
||||
/// Consider this example where a lookup table is used to validate whether a given character is within a specific set:
|
||||
/// <code>
|
||||
/// public static ReadOnlySpan<bool> ValidSetLookupTable => new bool[]
|
||||
/// {
|
||||
/// false, true, true, true, true, true, false, true,
|
||||
/// false, false, true, false, true, false, true, false,
|
||||
/// true, false, false, true, false, false, false, false,
|
||||
/// false, false, false, false, true, true, false, true
|
||||
/// };
|
||||
///
|
||||
/// int ch = Console.Read();
|
||||
/// bool isValid = ValidSetLookupTable.DangerousGetLookupReference(ch);
|
||||
/// </code>
|
||||
/// Even if the input index is outside the range of the lookup table, being clamped to 0, it will
|
||||
/// just cause the value 0 to be returned in this case, which is functionally the same for the check
|
||||
/// being performed. This extension can easily be used whenever the first position in a lookup
|
||||
/// table being referenced corresponds to a falsey value, like in this case.
|
||||
/// Additionally, the example above leverages a compiler optimization introduced with C# 7.3,
|
||||
/// which allows <see cref="ReadOnlySpan{T}"/> instances pointing to compile-time constant data
|
||||
/// to be directly mapped to the static .text section in the final assembly: the array being
|
||||
/// created in code will never actually be allocated, and the <see cref="ReadOnlySpan{T}"/> will
|
||||
/// just point to constant data. Note that this only works for blittable values that are not
|
||||
/// dependent on the byte endianness of the system, like <see cref="byte"/> or <see cref="bool"/>.
|
||||
/// For more info, see <see href="https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static/"/>.
|
||||
/// </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"/>,
|
||||
/// or a reference to the first element within <paramref name="span"/> if <paramref name="i"/> was not a valid index.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
|
||||
{
|
||||
// Check whether the input is in range by first casting both
|
||||
// operands to uint and then comparing them, as this allows
|
||||
// the test to also identify cases where the input index is
|
||||
// less than zero. The resulting bool is then reinterpreted
|
||||
// as a byte (either 1 or 0), and then decremented.
|
||||
// This will result in either 0 if the input index was
|
||||
// valid for the target span, or -1 (0xFFFFFFFF) otherwise.
|
||||
// The result is then negated, producing the value 0xFFFFFFFF
|
||||
// for valid indices, or 0 otherwise. The generated mask
|
||||
// is then combined with the original index. This leaves
|
||||
// the index intact if it was valid, otherwise zeros it.
|
||||
// The computed offset is finally used to access the
|
||||
// lookup table, and it is guaranteed to never go out of
|
||||
// bounds unless the input span was just empty, which for a
|
||||
// lookup table can just be assumed to always be false.
|
||||
bool isInRange = (uint)i < (uint)span.Length;
|
||||
byte rangeFlag = *(byte*)&isInRange;
|
||||
uint
|
||||
negativeFlag = unchecked(rangeFlag - 1u),
|
||||
mask = ~negativeFlag,
|
||||
offset = (uint)i & mask;
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)offset);
|
||||
|
||||
return ref r1;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int height, int width)
|
||||
{
|
||||
return new(span, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new(span, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element of a given <see cref="ReadOnlySpan{T}"/> from its reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the input <see cref="ReadOnlySpan{T}"/>.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> to calculate the index for.</param>
|
||||
/// <param name="value">The reference to the target item to get the index for.</param>
|
||||
/// <returns>The index of <paramref name="value"/> within <paramref name="span"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T r1 = ref Unsafe.AsRef(value);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1);
|
||||
|
||||
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
|
||||
|
||||
if ((nuint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return (int)elementOffset;
|
||||
}
|
||||
|
||||
/// <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.AggressiveInlining)]
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return (int)SpanHelper.Count(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlySpan{T}"/> of bytes.
|
||||
/// </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="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"/>.
|
||||
/// </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>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(this ReadOnlySpan<TFrom> span)
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
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(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(span, separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the Djb2 algorithm.
|
||||
/// It was designed by <see href="https://en.wikipedia.org/wiki/Daniel_J._Bernstein">Daniel J. Bernstein</see> and is a
|
||||
/// <see href="https://en.wikipedia.org/wiki/List_of_hash_functions#Non-cryptographic_hash_functions">non-cryptographic has function</see>.
|
||||
/// The main advantages of this algorithm are a good distribution of the resulting hash codes, which results in a relatively low
|
||||
/// number of collisions, while at the same time being particularly fast to process, making it suitable for quickly hashing
|
||||
/// even long sequences of values. For the reference implementation, see: <see href="http://www.cse.yorku.ca/~oz/hash.html"/>.
|
||||
/// For details on the used constants, see the details provided in this StackOverflow answer (as well as the accepted one):
|
||||
/// <see href="https://stackoverflow.com/questions/10696223/reason-for-5381-number-in-djb-hash-function/13809282#13809282"/>.
|
||||
/// Additionally, a comparison between some common hashing algorithms can be found in the reply to this StackExchange question:
|
||||
/// <see href="https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed"/>.
|
||||
/// Note that the exact implementation is slightly different in this method when it is not called on a sequence of <see cref="byte"/>
|
||||
/// values: in this case the <see cref="object.GetHashCode"/> method will be invoked for each <typeparamref name="T"/> value in
|
||||
/// the provided <see cref="ReadOnlySpan{T}"/> instance, and then those values will be combined 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]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
|
||||
where T : notnull
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
destination.CopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the contents of a given <see cref="ReadOnlySpan{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryCopyTo<T>(this ReadOnlySpan<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
return destination.TryCopyFrom(span);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to an element at a specified index within a given <see cref="Span{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
|
||||
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
|
||||
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, nint i)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int height, int width)
|
||||
{
|
||||
return new(span, height, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int offset, int height, int width, int pitch)
|
||||
{
|
||||
return new(span, offset, height, width, pitch);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> of bytes.
|
||||
/// </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="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"/>.
|
||||
/// </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>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Span<TTo> Cast<TFrom, TTo>(this Span<TFrom> span)
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<TFrom, TTo>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an element of a given <see cref="Span{T}"/> from its reference.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type if items in the input <see cref="Span{T}"/>.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> to calculate the index for.</param>
|
||||
/// <param name="value">The reference to the target item to get the index for.</param>
|
||||
/// <returns>The index of <paramref name="value"/> within <paramref name="span"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int IndexOf<T>(this Span<T> span, ref T value)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref value);
|
||||
|
||||
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
|
||||
|
||||
if ((nuint)elementOffset >= (uint)span.Length)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
return (int)elementOffset;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return (int)SpanHelper.Count(ref r0, length, 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(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(span, separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="Span{T}"/> instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <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
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
nint length = (nint)(uint)span.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the destination <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="Span{T}"/>.
|
||||
/// </exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
destination.CopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the contents of a given <see cref="Span{T}"/> into destination <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
|
||||
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
|
||||
/// <param name="destination">The <see cref="RefEnumerable{T}"/> instance to copy items into.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryCopyTo<T>(this Span<T> span, RefEnumerable<T> destination)
|
||||
{
|
||||
return destination.TryCopyFrom(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
|
||||
/// </summary>
|
||||
internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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(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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly 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 SPAN_RUNTIME_SUPPORT
|
||||
/// <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(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(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>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly 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 SPAN_RUNTIME_SUPPORT
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Stream"/> type.
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Asynchronously reads a sequence of bytes from a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source <see cref="Stream"/> to read data from.</param>
|
||||
/// <param name="buffer">The destination <see cref="Memory{T}"/> to write data to.</param>
|
||||
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the operation being performed.</returns>
|
||||
public static ValueTask<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
|
||||
}
|
||||
|
||||
// If the memory wraps an array, extract it and use it directly
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
return new ValueTask<int>(stream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken));
|
||||
}
|
||||
|
||||
// Local function used as the fallback path. This happens when the input memory
|
||||
// doesn't wrap an array instance we can use. We use a local function as we need
|
||||
// the body to be asynchronous, in order to execute the finally block after the
|
||||
// write operation has been completed. By separating the logic, we can keep the
|
||||
// main method as a synchronous, value-task returning function. This fallback
|
||||
// path should hopefully be pretty rare, as memory instances are typically just
|
||||
// created around arrays, often being rented from a memory pool in particular.
|
||||
static async Task<int> ReadAsyncFallback(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(rent, 0, buffer.Length, cancellationToken);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer.Span);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTask<int>(ReadAsyncFallback(stream, buffer, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes a sequence of bytes to a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The destination <see cref="Stream"/> to write data to.</param>
|
||||
/// <param name="buffer">The source <see cref="ReadOnlyMemory{T}"/> to read data from.</param>
|
||||
/// <param name="cancellationToken">The optional <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the operation being performed.</returns>
|
||||
public static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new ValueTask(Task.FromCanceled(cancellationToken));
|
||||
}
|
||||
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
return new ValueTask(stream.WriteAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken));
|
||||
}
|
||||
|
||||
// Local function, same idea as above
|
||||
static async Task WriteAsyncFallback(Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
buffer.Span.CopyTo(rent);
|
||||
|
||||
await stream.WriteAsync(rent, 0, buffer.Length, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
return new ValueTask(WriteAsyncFallback(stream, buffer, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The source <see cref="Stream"/> to read data from.</param>
|
||||
/// <param name="buffer">The target <see cref="Span{T}"/> to write data to.</param>
|
||||
/// <returns>The number of bytes that have been read.</returns>
|
||||
public static int Read(this Stream stream, Span<byte> buffer)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = stream.Read(rent, 0, buffer.Length);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to a given <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="stream">The destination <see cref="Stream"/> to write data to.</param>
|
||||
/// <param name="buffer">The source <see cref="Span{T}"/> to read data from.</param>
|
||||
public static void Write(this Stream stream, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
byte[] rent = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
|
||||
try
|
||||
{
|
||||
buffer.CopyTo(rent);
|
||||
|
||||
stream.Write(rent, 0, buffer.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rent);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value of a specified type from a source <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to read.</typeparam>
|
||||
/// <param name="stream">The source <see cref="Stream"/> instance to read from.</param>
|
||||
/// <returns>The <typeparamref name="T"/> value read from <paramref name="stream"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <paramref name="stream"/> reaches the end.</exception>
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public static T Read<T>(this Stream stream)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
T result = default;
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
|
||||
unsafe
|
||||
{
|
||||
if (stream.Read(new Span<byte>(&result, length)) != length)
|
||||
{
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
try
|
||||
{
|
||||
if (stream.Read(buffer, 0, length) != length)
|
||||
{
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
|
||||
return Unsafe.ReadUnaligned<T>(ref buffer[0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value of a specified type into a target <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to write.</typeparam>
|
||||
/// <param name="stream">The target <see cref="Stream"/> instance to write to.</param>
|
||||
/// <param name="value">The input value to write to <paramref name="stream"/>.</param>
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public static void Write<T>(this Stream stream, in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref Unsafe.AsRef(value);
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref r0);
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
|
||||
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpan(ref r1, length);
|
||||
|
||||
stream.Write(span);
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
try
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref buffer[0], value);
|
||||
|
||||
stream.Write(buffer, 0, length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> when <see cref="Read{T}"/> fails.
|
||||
/// </summary>
|
||||
private static void ThrowInvalidOperationExceptionForEndOfStream()
|
||||
{
|
||||
throw new InvalidOperationException("The stream didn't contain enough data to read the requested item");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCOREAPP2_1 || NETSTANDARD
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
return ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
var stringData = Unsafe.As<RawStringData>(text)!;
|
||||
|
||||
return ref stringData.Data;
|
||||
#else
|
||||
return ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
|
||||
#elif NETCOREAPP2_1
|
||||
ref char r0 = ref Unsafe.As<RawStringData>(text)!.Data;
|
||||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
#if NETCOREAPP2_1
|
||||
// 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
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
nint length = (nint)(uint)text.Length;
|
||||
|
||||
return (int)SpanHelper.Count(ref r0, length, 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;
|
||||
/// char 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(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(text.AsSpan(), separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a content hash from the input <see cref="string"/> instance using the Djb2 algorithm.
|
||||
/// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>.
|
||||
/// </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 unsafe int GetDjb2HashCode(this string text)
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReference();
|
||||
nint length = (nint)(uint)text.Length;
|
||||
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,467 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
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 (in [0, 31] range).</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 unsafe 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 *(bool*)&flag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given bit is set in a given bitwise lookup table.
|
||||
/// This method provides a branchless, register-based (with no memory accesses) way to
|
||||
/// check whether a given value is valid, according to a precomputed lookup table.
|
||||
/// It is similar in behavior to <see cref="HasFlag(uint,int)"/>, with the main difference
|
||||
/// being that this method will also validate the input <paramref name="x"/> parameter, and
|
||||
/// will always return <see langword="false"/> if it falls outside of the expected interval.
|
||||
/// Additionally, this method accepts a <paramref name="min"/> parameter, which is used to
|
||||
/// decrement the input parameter <paramref name="x"/> to ensure that the range of accepted
|
||||
/// values fits within the available 32 bits of the lookup table in use.
|
||||
/// For more info on this optimization technique, see <see href="https://egorbo.com/llvm-range-checks.html"/>.
|
||||
/// Here is how the code from the link above would be implemented using this method:
|
||||
/// <code>
|
||||
/// bool IsReservedCharacter(char c)
|
||||
/// {
|
||||
/// return BitHelper.HasLookupFlag(314575237u, c, 36);
|
||||
/// }
|
||||
/// </code>
|
||||
/// The resulted assembly is virtually identical, with the added optimization that the one
|
||||
/// produced by <see cref="HasLookupFlag(uint,int,int)"/> has no conditional branches at all.
|
||||
/// </summary>
|
||||
/// <param name="table">The input lookup table to use.</param>
|
||||
/// <param name="x">The input value to check.</param>
|
||||
/// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param>
|
||||
/// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns>
|
||||
/// <remarks>
|
||||
/// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/>
|
||||
/// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool HasLookupFlag(uint table, int x, int min = 0)
|
||||
{
|
||||
// First, the input value is scaled down by the given minimum.
|
||||
// This step will be skipped entirely if min is just the default of 0.
|
||||
// The valid range is given by 32, which is the number of bits in the
|
||||
// lookup table. The input value is first cast to uint so that if it was
|
||||
// negative, the check will fail as well. Then, the result of this
|
||||
// operation is used to compute a bitwise flag of either 0xFFFFFFFF if the
|
||||
// input is accepted, or all 0 otherwise. The target bit is then extracted,
|
||||
// and this value is combined with the previous mask. This is done so that
|
||||
// if the shift was performed with a value that was too high, which has an
|
||||
// undefined behavior and could produce a non-0 value, the mask will reset
|
||||
// the final value anyway. This result is then unchecked-cast to a byte (as
|
||||
// it is guaranteed to always be either 1 or 0), and then reinterpreted
|
||||
// as a bool just like in the HasFlag method above, and then returned.
|
||||
int i = x - min;
|
||||
bool isInRange = (uint)i < 32u;
|
||||
byte byteFlag = *(byte*)&isInRange;
|
||||
int
|
||||
negativeFlag = byteFlag - 1,
|
||||
mask = ~negativeFlag,
|
||||
shift = unchecked((int)((table >> i) & 1)),
|
||||
and = shift & mask;
|
||||
byte result = unchecked((byte)and);
|
||||
bool valid = *(bool*)&result;
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given value has any bytes that are set to 0.
|
||||
/// That is, given a <see cref="uint"/> value, which has a total of 4 bytes,
|
||||
/// it checks whether any of those have all the bits set to 0.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to check.</param>
|
||||
/// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns>
|
||||
/// <remarks>
|
||||
/// This method contains no branches.
|
||||
/// For more background on this subject, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasZeroByte(uint value)
|
||||
{
|
||||
return ((value - 0x0101_0101u) & ~value & 0x8080_8080u) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given value has any bytes that are set to 0.
|
||||
/// This method mirrors <see cref="HasZeroByte(uint)"/>, but with <see cref="ulong"/> values.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to check.</param>
|
||||
/// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasZeroByte(ulong value)
|
||||
{
|
||||
return ((value - 0x0101_0101_0101_0101ul) & ~value & 0x8080_8080_8080_8080ul) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a byte in the input <see cref="uint"/> value matches a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to check.</param>
|
||||
/// <param name="target">The target byte to look for.</param>
|
||||
/// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method contains no branches.
|
||||
/// For more info, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasByteEqualTo(uint value, byte target)
|
||||
{
|
||||
return HasZeroByte(value ^ (0x0101_0101u * target));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a byte in the input <see cref="uint"/> value matches a target value.
|
||||
/// This method mirrors <see cref="HasByteEqualTo(uint,byte)"/>, but with <see cref="ulong"/> values.
|
||||
/// </summary>
|
||||
/// <param name="value">The input value to check.</param>
|
||||
/// <param name="target">The target byte to look for.</param>
|
||||
/// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasByteEqualTo(ulong value, byte target)
|
||||
{
|
||||
return HasZeroByte(value ^ (0x0101_0101_0101_0101u * target));
|
||||
}
|
||||
|
||||
/// <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 (in [0, 31] range).</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 (in [0, 31] range).</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 unsafe 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,
|
||||
// then 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.
|
||||
bool copy = flag;
|
||||
uint
|
||||
flag32 = *(byte*)©,
|
||||
shift = flag32 << n,
|
||||
or = and | shift;
|
||||
|
||||
return or;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a bit field range from a given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <returns>The value of the extracted range within <paramref name="value"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>.
|
||||
/// If either parameter is not valid, the result will just be inconsistent. The method
|
||||
/// should not be used to set all the bits at once, and it is not guaranteed to work in
|
||||
/// that case, which would just be equivalent to assigning the <see cref="uint"/> value.
|
||||
/// Additionally, no conditional branches are used to retrieve the range.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ExtractRange(uint value, byte start, byte length)
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
if (Bmi1.IsSupported)
|
||||
{
|
||||
return Bmi1.BitFieldExtract(value, start, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1u << length) - 1u);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// 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 SetRange(ref uint value, byte start, byte length, uint flags)
|
||||
{
|
||||
value = SetRange(value, start, length, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial <see cref="uint"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 31] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <returns>The updated bit field value after setting the specified range.</returns>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// 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 SetRange(uint value, byte start, byte length, uint flags)
|
||||
{
|
||||
uint
|
||||
highBits = (1u << length) - 1u,
|
||||
loadMask = highBits << start,
|
||||
storeMask = (flags & highBits) << start;
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
if (Bmi1.IsSupported)
|
||||
{
|
||||
return Bmi1.AndNot(loadMask, value) | storeMask;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
|
||||
/// <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 (in [0, 63] range).</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 unsafe bool HasFlag(ulong value, int n)
|
||||
{
|
||||
// Same logic as the uint version, see that for more info
|
||||
byte flag = (byte)((value >> n) & 1);
|
||||
|
||||
return *(bool*)&flag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given bit is set in a given bitwise lookup table.
|
||||
/// For more info, check the XML docs of the <see cref="HasLookupFlag(uint,int,int)"/> overload.
|
||||
/// </summary>
|
||||
/// <param name="table">The input lookup table to use.</param>
|
||||
/// <param name="x">The input value to check.</param>
|
||||
/// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param>
|
||||
/// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns>
|
||||
/// <remarks>
|
||||
/// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/>
|
||||
/// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool HasLookupFlag(ulong table, int x, int min = 0)
|
||||
{
|
||||
int i = x - min;
|
||||
bool isInRange = (uint)i < 64u;
|
||||
byte byteFlag = *(byte*)&isInRange;
|
||||
int
|
||||
negativeFlag = byteFlag - 1,
|
||||
mask = ~negativeFlag,
|
||||
shift = unchecked((int)((table >> i) & 1)),
|
||||
and = shift & mask;
|
||||
byte result = unchecked((byte)and);
|
||||
bool valid = *(bool*)&result;
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <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 (in [0, 63] range).</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 (in [0, 63] range).</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 unsafe ulong SetFlag(ulong value, int n, bool flag)
|
||||
{
|
||||
ulong
|
||||
bit = 1ul << n,
|
||||
not = ~bit,
|
||||
and = value & not;
|
||||
bool copy = flag;
|
||||
ulong flag64 = *(byte*)©,
|
||||
shift = flag64 << n,
|
||||
or = and | shift;
|
||||
|
||||
return or;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a bit field range from a given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The input <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <returns>The value of the extracted range within <paramref name="value"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>.
|
||||
/// If either parameter is not valid, the result will just be inconsistent. The method
|
||||
/// should not be used to set all the bits at once, and it is not guaranteed to work in
|
||||
/// that case, which would just be equivalent to assigning the <see cref="ulong"/> value.
|
||||
/// Additionally, no conditional branches are used to retrieve the range.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong ExtractRange(ulong value, byte start, byte length)
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
if (Bmi1.X64.IsSupported)
|
||||
{
|
||||
return Bmi1.X64.BitFieldExtract(value, start, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1ul << length) - 1ul);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The target <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// 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 SetRange(ref ulong value, byte start, byte length, ulong flags)
|
||||
{
|
||||
value = SetRange(value, start, length, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a bit field range within a target value.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial <see cref="ulong"/> value.</param>
|
||||
/// <param name="start">The initial index of the range to extract (in [0, 63] range).</param>
|
||||
/// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param>
|
||||
/// <param name="flags">The input flags to insert in the target range.</param>
|
||||
/// <returns>The updated bit field value after setting the specified range.</returns>
|
||||
/// <remarks>
|
||||
/// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters
|
||||
/// 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 SetRange(ulong value, byte start, byte length, ulong flags)
|
||||
{
|
||||
ulong
|
||||
highBits = (1ul << length) - 1ul,
|
||||
loadMask = highBits << start,
|
||||
storeMask = (flags & highBits) << start;
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
if (Bmi1.X64.IsSupported)
|
||||
{
|
||||
return Bmi1.X64.AndNot(loadMask, value) | storeMask;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if !NETSTANDARD1_4
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
/// <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>
|
||||
/// <remarks>
|
||||
/// The hash codes returned by the <see cref="Combine"/> method are only guaranteed to be repeatable for
|
||||
/// the current execution session, just like with the available <see cref="HashCode"/> APIs.In other words,
|
||||
/// hashing the same <see cref="ReadOnlySpan{T}"/> collection multiple times in the same process will always
|
||||
/// result in the same hash code, while the same collection being hashed again from another process
|
||||
/// (or another instance of the same process) is not guaranteed to result in the same final value.
|
||||
/// For more info, see <see href="https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks"/>.
|
||||
/// </remarks>
|
||||
public struct HashCode<T>
|
||||
where T : notnull
|
||||
{
|
||||
/// <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)]
|
||||
internal static int CombineValues(ReadOnlySpan<T> span)
|
||||
{
|
||||
ref T r0 = ref MemoryMarshal.GetReference(span);
|
||||
|
||||
// If typeof(T) is not unmanaged, iterate over all the items one by one.
|
||||
// This check is always known in advance either by the JITter or by the AOT
|
||||
// compiler, so this branch will never actually be executed by the code.
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
{
|
||||
return SpanHelper.GetDjb2HashCode(ref r0, (nint)(uint)span.Length);
|
||||
}
|
||||
|
||||
// Get the info for the target memory area to process.
|
||||
// The line below is computing the total byte size for the span,
|
||||
// and we cast both input factors to uint first to avoid sign extensions
|
||||
// (they're both guaranteed to always be positive values), and to let the
|
||||
// JIT avoid the 64 bit computation entirely when running in a 32 bit
|
||||
// process. In that case it will just compute the byte size as a 32 bit
|
||||
// multiplication with overflow, which is guaranteed never to happen anyway.
|
||||
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
|
||||
nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
|
||||
|
||||
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
using static System.Numerics.BitOperations;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for intrinsic bit-twiddling operations. The methods use hardware intrinsics
|
||||
/// when available on the underlying platform, otherwise they use optimized software fallbacks.
|
||||
/// </summary>
|
||||
internal static class BitOperations
|
||||
{
|
||||
/// <summary>
|
||||
/// Rounds up an <see cref="int"/> value to a power of 2.
|
||||
/// </summary>
|
||||
/// <param name="x">The input value to round up.</param>
|
||||
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RoundUpPowerOfTwo(int x)
|
||||
{
|
||||
#if NETCOREAPP3_1 || NET5_0
|
||||
return 1 << (32 - LeadingZeroCount((uint)(x - 1)));
|
||||
#else
|
||||
x--;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x++;
|
||||
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference with a given step.
|
||||
/// </summary>
|
||||
internal static class RefEnumerableHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears a target memory area.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values to clear.</typeparam>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The number of items in the memory area.</param>
|
||||
/// <param name="step">The number of items between each consecutive target value.</param>
|
||||
public static void Clear<T>(ref T r0, nint length, nint step)
|
||||
{
|
||||
nint offset = 0;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
|
||||
length -= 8;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
Unsafe.Add(ref r0, offset += step) = default!;
|
||||
|
||||
length -= 4;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
// Clear the remaining values
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
|
||||
length -= 1;
|
||||
offset += step;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 8;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 4;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
/// <param name="destinationStep">The step between consecutive items in the memory area pointed to by <paramref name="destinationRef"/>.</param>
|
||||
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep, nint destinationStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += sourceStep;
|
||||
destinationOffset += destinationStep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a sequence of discontiguous items from one memory area to another. This mirrors
|
||||
/// <see cref="CopyTo{T}(ref T,ref T,nint,nint)"/>, but <paramref name="sourceStep"/> refers to <paramref name="destinationRef"/> instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to copy.</typeparam>
|
||||
/// <param name="sourceRef">The source reference to copy from.</param>
|
||||
/// <param name="destinationRef">The target reference to copy to.</param>
|
||||
/// <param name="length">The total number of items to copy.</param>
|
||||
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
|
||||
public static void CopyFrom<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 4);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 5);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 6);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 7);
|
||||
|
||||
length -= 8;
|
||||
sourceOffset += 8;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
|
||||
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
|
||||
|
||||
length -= 4;
|
||||
sourceOffset += 4;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
|
||||
|
||||
length -= 1;
|
||||
sourceOffset += 1;
|
||||
destinationOffset += sourceStep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a target memory area.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values to fill.</typeparam>
|
||||
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
|
||||
/// <param name="length">The number of items in the memory area.</param>
|
||||
/// <param name="step">The number of items between each consecutive target value.</param>
|
||||
/// <param name="value">The value to assign to every item in the target memory area.</param>
|
||||
public static void Fill<T>(ref T r0, nint length, nint step, T value)
|
||||
{
|
||||
nint offset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
|
||||
length -= 8;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
Unsafe.Add(ref r0, offset += step) = value;
|
||||
|
||||
length -= 4;
|
||||
offset += step;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
Unsafe.Add(ref r0, offset) = value;
|
||||
|
||||
length -= 1;
|
||||
offset += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma warning disable SA1512
|
||||
|
||||
// The portable implementation in this type is originally from CoreFX.
|
||||
// See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that with utility methods for dealing with references, and other low-level details.
|
||||
/// It also contains some APIs that act as polyfills for .NET Standard 2.0 and below.
|
||||
/// </summary>
|
||||
internal static class RuntimeHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a length of items from one size to another (rounding towards zero).
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">The source type of items.</typeparam>
|
||||
/// <typeparam name="TTo">The target type of items.</typeparam>
|
||||
/// <param name="length">The input length to convert.</param>
|
||||
/// <returns>The converted length for the specified argument and types.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int ConvertLength<TFrom, TTo>(int length)
|
||||
where TFrom : unmanaged
|
||||
where TTo : unmanaged
|
||||
{
|
||||
if (sizeof(TFrom) == sizeof(TTo))
|
||||
{
|
||||
return length;
|
||||
}
|
||||
else if (sizeof(TFrom) == 1)
|
||||
{
|
||||
return (int)((uint)length / (uint)sizeof(TTo));
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong targetLength = (ulong)(uint)length * (uint)sizeof(TFrom) / (uint)sizeof(TTo);
|
||||
|
||||
return checked((int)targetLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a given array as a native integer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <param name="array">The input <see cref="Array"/> instance.</param>
|
||||
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
|
||||
/// <remarks>
|
||||
/// This method is needed because this expression is not inlined correctly if the target array
|
||||
/// is only visible as a non-generic <see cref="Array"/> instance, because the C# compiler will
|
||||
/// not be able to emit the <see langword="ldlen"/> opcode instead of calling the right method.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint GetArrayNativeLength<T>(T[] array)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
// .NET Standard 1.4 doesn't include the API to get the long length, so
|
||||
// we just cast the length and throw in case the array is larger than
|
||||
// int.MaxValue. There's not much we can do in this specific case.
|
||||
return (nint)(uint)array.Length;
|
||||
#else
|
||||
return (nint)array.LongLength;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a given array as a native integer.
|
||||
/// </summary>
|
||||
/// <param name="array">The input <see cref="Array"/> instance.</param>
|
||||
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static nint GetArrayNativeLength(Array array)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return (nint)(uint)array.Length;
|
||||
#else
|
||||
return (nint)array.LongLength;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a SZ array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a SZ array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArrayDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.ArrayDataByteOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 2D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 2D array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArray2DDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.Array2DDataByteOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 3D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 3D array.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr GetArray3DDataByteOffset<T>()
|
||||
{
|
||||
return TypeInfo<T>.Array3DDataByteOffset;
|
||||
}
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Gets a byte offset describing a portable pinnable reference. This can either be an
|
||||
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
|
||||
/// and a reference to some of its data), or a raw pointer (described with a <see langword="null"/>
|
||||
/// reference to an <see cref="object"/>, and a reference that is assumed to refer to pinned data).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of field being referenced.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
|
||||
/// for the parameter <paramref name="obj"/>, or the value of the raw pointer passed as a tracked reference.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe IntPtr GetObjectDataOrReferenceByteOffset<T>(object? obj, ref T data)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return (IntPtr)Unsafe.AsPointer(ref data);
|
||||
}
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(obj, ref data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference from data describing a portable pinnable reference. This can either be an
|
||||
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
|
||||
/// and a byte offset into its data), or a raw pointer (described with a <see langword="null"/>
|
||||
/// reference to an <see cref="object"/>, and a byte offset representing the value of the raw pointer).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
|
||||
/// <returns>A <typeparamref name="T"/> reference matching the given parameters.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T GetObjectDataAtOffsetOrPointerReference<T>(object? obj, IntPtr offset)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return ref Unsafe.AsRef<T>((void*)offset);
|
||||
}
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(obj, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given type is a reference type or contains references.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to check.</typeparam>
|
||||
/// <returns>Whether or not <typeparamref name="T"/> respects the <see langword="unmanaged"/> constraint.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsReferenceOrContainsReferences<T>()
|
||||
{
|
||||
return TypeInfo<T>.IsReferenceOrContainsReferences;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the logic for <see cref="IsReferenceOrContainsReferences{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The current type to check.</param>
|
||||
/// <returns>Whether or not <paramref name="type"/> is a reference type or contains references.</returns>
|
||||
[Pure]
|
||||
private static bool IsReferenceOrContainsReferences(Type type)
|
||||
{
|
||||
// Common case, for primitive types
|
||||
if (type.GetTypeInfo().IsPrimitive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!type.GetTypeInfo().IsValueType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the type is Nullable<T>
|
||||
if (Nullable.GetUnderlyingType(type) is Type nullableType)
|
||||
{
|
||||
type = nullableType;
|
||||
}
|
||||
|
||||
if (type.GetTypeInfo().IsEnum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Complex struct, recursively inspect all fields
|
||||
foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields)
|
||||
{
|
||||
if (field.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsReferenceOrContainsReferences(field.FieldType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A private generic class to preload type info for arbitrary runtime types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to load info for.</typeparam>
|
||||
private static class TypeInfo<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
|
||||
|
||||
#if !SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
|
||||
/// </summary>
|
||||
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArrayDataByteOffset()
|
||||
{
|
||||
var array = new T[1];
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="Array2DDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="Array2DDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArray2DDataByteOffset()
|
||||
{
|
||||
var array = new T[1, 1];
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the value for <see cref="Array3DDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <returns>The value of <see cref="Array3DDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArray3DDataByteOffset()
|
||||
{
|
||||
var array = new T[1, 1, 1];
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0, 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,368 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <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)]
|
||||
public static nint Count<T>(ref T r0, nint length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
if (!Vector.IsHardwareAccelerated)
|
||||
{
|
||||
return CountSequential(ref r0, length, value);
|
||||
}
|
||||
|
||||
// Special vectorized version when using a supported type
|
||||
if (typeof(T) == typeof(byte) ||
|
||||
typeof(T) == typeof(sbyte) ||
|
||||
typeof(T) == typeof(bool))
|
||||
{
|
||||
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
|
||||
sbyte target = Unsafe.As<T, sbyte>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(char) ||
|
||||
typeof(T) == typeof(ushort) ||
|
||||
typeof(T) == typeof(short))
|
||||
{
|
||||
ref short r1 = ref Unsafe.As<T, short>(ref r0);
|
||||
short target = Unsafe.As<T, short>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int) ||
|
||||
typeof(T) == typeof(uint))
|
||||
{
|
||||
ref int r1 = ref Unsafe.As<T, int>(ref r0);
|
||||
int target = Unsafe.As<T, int>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long) ||
|
||||
typeof(T) == typeof(ulong))
|
||||
{
|
||||
ref long r1 = ref Unsafe.As<T, long>(ref r0);
|
||||
long target = Unsafe.As<T, long>(ref value);
|
||||
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
return CountSequential(ref r0, length, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="Count{T}"/> with a sequential search.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
private static nint CountSequential<T>(ref T r0, nint length, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
|
||||
// Main loop with 8 unrolled iterations
|
||||
while (length >= 8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while (length > 0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="Count{T}"/> with a vectorized search.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
private static nint CountSimd<T>(ref T r0, nint length, T value)
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
|
||||
// Skip the initialization overhead if there are not enough items
|
||||
if (length >= Vector<T>.Count)
|
||||
{
|
||||
var vc = new Vector<T>(value);
|
||||
|
||||
do
|
||||
{
|
||||
// Calculate the maximum sequential area that can be processed in
|
||||
// one pass without the risk of numeric overflow in the dot product
|
||||
// to sum the partial results. We also backup the current offset to
|
||||
// be able to track how many items have been processed, which lets
|
||||
// us avoid updating a third counter (length) in the loop body.
|
||||
nint
|
||||
max = GetUpperBound<T>(),
|
||||
chunkLength = length <= max ? length : max,
|
||||
initialOffset = offset;
|
||||
|
||||
var partials = Vector<T>.Zero;
|
||||
|
||||
// Unrolled vectorized loop, with 8 unrolled iterations. We only run this when the
|
||||
// current type T is at least 2 bytes in size, otherwise the average chunk length
|
||||
// would always be too small to be able to trigger the unrolled loop, and the overall
|
||||
// performance would just be slightly worse due to the additional conditional branches.
|
||||
if (typeof(T) != typeof(sbyte))
|
||||
{
|
||||
while (chunkLength >= Vector<T>.Count * 8)
|
||||
{
|
||||
ref T ri0 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 0));
|
||||
var vi0 = Unsafe.As<T, Vector<T>>(ref ri0);
|
||||
var ve0 = Vector.Equals(vi0, vc);
|
||||
|
||||
partials -= ve0;
|
||||
|
||||
ref T ri1 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 1));
|
||||
var vi1 = Unsafe.As<T, Vector<T>>(ref ri1);
|
||||
var ve1 = Vector.Equals(vi1, vc);
|
||||
|
||||
partials -= ve1;
|
||||
|
||||
ref T ri2 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 2));
|
||||
var vi2 = Unsafe.As<T, Vector<T>>(ref ri2);
|
||||
var ve2 = Vector.Equals(vi2, vc);
|
||||
|
||||
partials -= ve2;
|
||||
|
||||
ref T ri3 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 3));
|
||||
var vi3 = Unsafe.As<T, Vector<T>>(ref ri3);
|
||||
var ve3 = Vector.Equals(vi3, vc);
|
||||
|
||||
partials -= ve3;
|
||||
|
||||
ref T ri4 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 4));
|
||||
var vi4 = Unsafe.As<T, Vector<T>>(ref ri4);
|
||||
var ve4 = Vector.Equals(vi4, vc);
|
||||
|
||||
partials -= ve4;
|
||||
|
||||
ref T ri5 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 5));
|
||||
var vi5 = Unsafe.As<T, Vector<T>>(ref ri5);
|
||||
var ve5 = Vector.Equals(vi5, vc);
|
||||
|
||||
partials -= ve5;
|
||||
|
||||
ref T ri6 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 6));
|
||||
var vi6 = Unsafe.As<T, Vector<T>>(ref ri6);
|
||||
var ve6 = Vector.Equals(vi6, vc);
|
||||
|
||||
partials -= ve6;
|
||||
|
||||
ref T ri7 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 7));
|
||||
var vi7 = Unsafe.As<T, Vector<T>>(ref ri7);
|
||||
var ve7 = Vector.Equals(vi7, vc);
|
||||
|
||||
partials -= ve7;
|
||||
|
||||
chunkLength -= Vector<T>.Count * 8;
|
||||
offset += Vector<T>.Count * 8;
|
||||
}
|
||||
}
|
||||
|
||||
while (chunkLength >= Vector<T>.Count)
|
||||
{
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
// 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 just 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;
|
||||
|
||||
chunkLength -= Vector<T>.Count;
|
||||
offset += Vector<T>.Count;
|
||||
}
|
||||
|
||||
result += CastToNativeInt(Vector.Dot(partials, Vector<T>.One));
|
||||
length -= offset - initialOffset;
|
||||
}
|
||||
while (length >= Vector<T>.Count);
|
||||
}
|
||||
|
||||
// Optional 8 unrolled iterations. This is only done when a single SIMD
|
||||
// register can contain over 8 values of the current type, as otherwise
|
||||
// there could never be enough items left after the vectorized path
|
||||
if (Vector<T>.Count > 8 &&
|
||||
length >= 8)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToByte();
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Optional 4 unrolled iterations
|
||||
if (Vector<T>.Count > 4 &&
|
||||
length >= 4)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToByte();
|
||||
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToByte();
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Iterate over the remaining values and count those that match
|
||||
while (length > 0)
|
||||
{
|
||||
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upper bound for partial sums with a given <typeparamref name="T"/> parameter.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type argument currently in use.</typeparam>
|
||||
/// <returns>The native <see cref="int"/> value representing the upper bound.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe nint GetUpperBound<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return sbyte.MaxValue;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return short.MaxValue;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
if (sizeof(nint) == sizeof(int))
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
// If we are on a 64 bit architecture and we are counting with a SIMD vector of 64
|
||||
// bit values, we can use long.MaxValue as the upper bound, as a native integer will
|
||||
// be able to contain such a value with no overflows. This will allow the count tight
|
||||
// loop to process all the items in the target area in a single pass (except the mod).
|
||||
// The (void*) cast is necessary to ensure the right constant is produced on runtimes
|
||||
// before .NET 5 that don't natively support C# 9. For instance, removing that (void*)
|
||||
// cast results in the value 0xFFFFFFFFFFFFFFFF (-1) instead of 0x7FFFFFFFFFFFFFFFF.
|
||||
return (nint)(void*)long.MaxValue;
|
||||
}
|
||||
|
||||
throw null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a value of a given type to a native <see cref="int"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The input type to cast.</typeparam>
|
||||
/// <param name="value">The input <typeparamref name="T"/> value to cast to native <see cref="int"/>.</param>
|
||||
/// <returns>The native <see cref="int"/> cast of <paramref name="value"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nint CastToNativeInt<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return (byte)(sbyte)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return (ushort)(short)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
{
|
||||
return (nint)(uint)(int)(object)value;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
return (nint)(ulong)(long)(object)value;
|
||||
}
|
||||
|
||||
throw null!;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,315 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the djb2 hash for the target sequence of items of a given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to hash.</typeparam>
|
||||
/// <param name="r0">The reference to the target memory area to hash.</param>
|
||||
/// <param name="length">The number of items to hash.</param>
|
||||
/// <returns>The Djb2 value for the input sequence of items.</returns>
|
||||
[Pure]
|
||||
public static int GetDjb2HashCode<T>(ref T r0, nint length)
|
||||
where T : notnull
|
||||
{
|
||||
int hash = 5381;
|
||||
nint offset = 0;
|
||||
|
||||
while (length >= 8)
|
||||
{
|
||||
// Doing a left shift by 5 and adding is equivalent to multiplying by 33.
|
||||
// This is preferred for performance reasons, as when working with integer
|
||||
// values most CPUs have higher latency for multiplication operations
|
||||
// compared to a simple shift and add. For more info on this, see the
|
||||
// details for imul, shl, add: https://gmplib.org/~tege/x86-timing.pdf.
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 4).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 5).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 6).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 7).GetHashCode());
|
||||
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (length >= 4)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
|
||||
|
||||
length -= 4;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <remarks>
|
||||
/// While this method is similar to <see cref="GetDjb2HashCode{T}"/> and can in some cases
|
||||
/// produce the same output for a given memory area, it is not guaranteed to always be that way.
|
||||
/// This is because this method can use SIMD instructions if possible, which can cause a computed
|
||||
/// hash to differ for the same data, if processed on different machines with different CPU features.
|
||||
/// The advantage of this method is that when SIMD instructions are available, it performs much
|
||||
/// faster than <see cref="GetDjb2HashCode{T}"/>, as it can parallelize much of the workload.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length)
|
||||
{
|
||||
int hash = 5381;
|
||||
nint offset = 0;
|
||||
|
||||
// Check whether SIMD instructions are supported, and also check
|
||||
// whether we have enough data to perform at least one unrolled
|
||||
// iteration of the vectorized path. This heuristics is to balance
|
||||
// the overhead of loading the constant values in the two registers,
|
||||
// and the final loop to combine the partial hash values.
|
||||
// Note that even when we use the vectorized path we don't need to do
|
||||
// any preprocessing to try to get memory aligned, as that would cause
|
||||
// the hash codes to potentially be different for the same data.
|
||||
if (Vector.IsHardwareAccelerated &&
|
||||
length >= (Vector<byte>.Count << 3))
|
||||
{
|
||||
var vh = new Vector<int>(5381);
|
||||
var v33 = new Vector<int>(33);
|
||||
|
||||
// First vectorized loop, with 8 unrolled iterations.
|
||||
// Assuming 256-bit registers (AVX2), a total of 256 bytes are processed
|
||||
// per iteration, with the partial hashes being accumulated for later use.
|
||||
while (length >= (Vector<byte>.Count << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 0));
|
||||
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
var vp0 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp0, vi0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 1));
|
||||
var vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
|
||||
var vp1 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp1, vi1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 2));
|
||||
var vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
|
||||
var vp2 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp2, vi2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 3));
|
||||
var vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
|
||||
var vp3 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp3, vi3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 4));
|
||||
var vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
|
||||
var vp4 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp4, vi4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 5));
|
||||
var vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
|
||||
var vp5 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp5, vi5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 6));
|
||||
var vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
|
||||
var vp6 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp6, vi6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 7));
|
||||
var vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
|
||||
var vp7 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp7, vi7);
|
||||
|
||||
length -= Vector<byte>.Count << 3;
|
||||
offset += Vector<byte>.Count << 3;
|
||||
}
|
||||
|
||||
// When this loop is reached, there are up to 255 bytes left (on AVX2).
|
||||
// Each iteration processed an additional 32 bytes and accumulates the results.
|
||||
while (length >= Vector<byte>.Count)
|
||||
{
|
||||
ref byte ri = ref Unsafe.Add(ref r0, offset);
|
||||
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
var vp = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp, vi);
|
||||
|
||||
length -= Vector<byte>.Count;
|
||||
offset += Vector<byte>.Count;
|
||||
}
|
||||
|
||||
// Combine the partial hash values in each position.
|
||||
// The loop below should automatically be unrolled by the JIT.
|
||||
for (var j = 0; j < Vector<int>.Count; j++)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ vh[j]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use the loop working with 64-bit values if we are on a
|
||||
// 64-bit processor, otherwise the result would be much slower.
|
||||
// Each unrolled iteration processes 64 bytes.
|
||||
if (sizeof(nint) == sizeof(ulong))
|
||||
{
|
||||
while (length >= (sizeof(ulong) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value0 ^ (int)(value0 >> 32));
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<ulong>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value1 ^ (int)(value1 >> 32));
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<ulong>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value2 ^ (int)(value2 >> 32));
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<ulong>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value3 ^ (int)(value3 >> 32));
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<ulong>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value4 ^ (int)(value4 >> 32));
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<ulong>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value5 ^ (int)(value5 >> 32));
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<ulong>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value6 ^ (int)(value6 >> 32));
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<ulong>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value7 ^ (int)(value7 >> 32));
|
||||
|
||||
length -= sizeof(ulong) << 3;
|
||||
offset += sizeof(ulong) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Each unrolled iteration processes 32 bytes
|
||||
while (length >= (sizeof(uint) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<uint>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<uint>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<uint>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<uint>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<uint>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<uint>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<uint>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ (int)value7);
|
||||
|
||||
length -= sizeof(uint) << 3;
|
||||
offset += sizeof(uint) << 3;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point (assuming AVX2), there will be up to 31 bytes
|
||||
// left, both for the vectorized and non vectorized paths.
|
||||
// That number would go up to 63 on AVX512 systems, in which case it is
|
||||
// still useful to perform this last loop unrolling.
|
||||
if (length >= (sizeof(ushort) << 3))
|
||||
{
|
||||
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0));
|
||||
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 1));
|
||||
var value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 2));
|
||||
var value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 3));
|
||||
var value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 4));
|
||||
var value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 5));
|
||||
var value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 6));
|
||||
var value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 7));
|
||||
var value7 = Unsafe.ReadUnaligned<ushort>(ref ri7);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value7);
|
||||
|
||||
length -= sizeof(ushort) << 3;
|
||||
offset += sizeof(ushort) << 3;
|
||||
}
|
||||
|
||||
// Handle the leftover items
|
||||
while (length > 0)
|
||||
{
|
||||
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset));
|
||||
|
||||
length -= 1;
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with <see cref="object"/> instances.
|
||||
/// </summary>
|
||||
public static class ObjectMarshal
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the byte offset to a specific field within a given <see cref="object"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of field being referenced.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
|
||||
/// for the parameter <paramref name="obj"/>. The offset is in relation to the first usable byte after the method table.
|
||||
/// </returns>
|
||||
/// <remarks>The input parameters are not validated, and it's responsibility of the caller to ensure that
|
||||
/// the <paramref name="data"/> reference is actually pointing to a memory location within <paramref name="obj"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IntPtr DangerousGetObjectDataByteOffset<T>(object obj, ref T data)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj)!;
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
|
||||
|
||||
return Unsafe.ByteOffset(ref r0, ref r1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <typeparamref name="T"/> reference to data within a given <see cref="object"/> at a specified offset.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
|
||||
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
||||
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
|
||||
/// <returns>A <typeparamref name="T"/> reference at a specified offset within <paramref name="obj"/>.</returns>
|
||||
/// <remarks>
|
||||
/// None of the input arguments is validated, and it is responsibility of the caller to ensure they are valid.
|
||||
/// In particular, using an invalid offset might cause the retrieved reference to be misaligned with the
|
||||
/// desired data, which would break the type system. Or, if the offset causes the retrieved reference to point
|
||||
/// to a memory location outside of the input <see cref="object"/> instance, that might lead to runtime crashes.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousGetObjectDataReferenceAt<T>(object obj, IntPtr offset)
|
||||
{
|
||||
var rawObj = Unsafe.As<RawObjectData>(obj)!;
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
|
||||
ref T r2 = ref Unsafe.As<byte, T>(ref r1);
|
||||
|
||||
return ref r2;
|
||||
}
|
||||
|
||||
// Description adapted from CoreCLR, see:
|
||||
// https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,301.
|
||||
// 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.
|
||||
// Even though the description above links to the CoreCLR source, this approach
|
||||
// can actually work on any .NET runtime, as it doesn't rely on a specific memory
|
||||
// layout. Even if some 3rd party .NET runtime had some additional fields in the
|
||||
// object header, before the field being referenced, the returned offset would still
|
||||
// be valid when used on instances of that particular type, as it's only being
|
||||
// used as a relative offset from the location pointed by the object reference.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private sealed class RawObjectData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
public byte Data;
|
||||
#pragma warning restore SA1401
|
||||
}
|
||||
|
||||
/// <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>(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>
|
||||
/// <exception cref="InvalidCastException">Thrown when <paramref name="obj"/> is not of type <typeparamref name="T"/>.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T DangerousUnbox<T>(object obj)
|
||||
where T : struct
|
||||
{
|
||||
return ref Unsafe.Unbox<T>(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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_OR_GREATER
|
||||
/// <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, in 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, in 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>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,347 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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_OR_GREATER
|
||||
/// <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>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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>
|
||||
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);
|
||||
ref TItem rStart = ref Unsafe.Add(ref r0, low);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref r0, end);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(in rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, int minimumActionsPerThread)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), minimumActionsPerThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
ForEach(memory, action, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
if (minimumActionsPerThread <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
|
||||
}
|
||||
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nint
|
||||
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
|
||||
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
|
||||
var actionInvoker = new InActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the batched operations in parallel
|
||||
Parallel.For(
|
||||
0,
|
||||
numBatches,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
|
||||
actionInvoker.Invoke);
|
||||
}
|
||||
|
||||
// Wrapping struct acting as explicit closure to execute the processing batches
|
||||
private readonly struct InActionInvokerWithReadOnlyMemory2D<TItem, TAction>
|
||||
where TAction : struct, IInAction<TItem>
|
||||
{
|
||||
private readonly int batchHeight;
|
||||
private readonly ReadOnlyMemory2D<TItem> memory;
|
||||
private readonly TAction action;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public InActionInvokerWithReadOnlyMemory2D(
|
||||
int batchHeight,
|
||||
ReadOnlyMemory2D<TItem> memory,
|
||||
in TAction action)
|
||||
{
|
||||
this.batchHeight = batchHeight;
|
||||
this.memory = memory;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(in rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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>
|
||||
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);
|
||||
ref TItem rStart = ref Unsafe.Add(ref r0, low);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref r0, end);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(ref rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, int minimumActionsPerThread)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, default(TAction), minimumActionsPerThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
ForEach(memory, action, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
|
||||
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
|
||||
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
|
||||
/// <param name="minimumActionsPerThread">
|
||||
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
|
||||
/// should be parallelized, or to a greater number if each individual invocation is fast
|
||||
/// enough that it is more efficient to set a lower bound per each running thread.
|
||||
/// </param>
|
||||
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
if (minimumActionsPerThread <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
|
||||
}
|
||||
|
||||
if (memory.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The underlying data for a Memory2D<T> instance is bound to int.MaxValue in both
|
||||
// axes, but its total size can exceed this value. Because of this, we calculate
|
||||
// the target chunks as nint to avoid overflows, and switch back to int values
|
||||
// for the rest of the setup, since the number of batches is bound to the number
|
||||
// of CPU cores (which is an int), and the height of each batch is necessarily
|
||||
// smaller than or equal than int.MaxValue, as it can't be greater than the
|
||||
// number of total batches, which again is capped at the number of CPU cores.
|
||||
nint
|
||||
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
|
||||
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
|
||||
var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the batched operations in parallel
|
||||
Parallel.For(
|
||||
0,
|
||||
numBatches,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
|
||||
actionInvoker.Invoke);
|
||||
}
|
||||
|
||||
// Wrapping struct acting as explicit closure to execute the processing batches
|
||||
private readonly struct RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>
|
||||
where TAction : struct, IRefAction<TItem>
|
||||
{
|
||||
private readonly int batchHeight;
|
||||
private readonly Memory2D<TItem> memory;
|
||||
private readonly TAction action;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefActionInvokerWithReadOnlyMemory2D(
|
||||
int batchHeight,
|
||||
Memory2D<TItem> memory,
|
||||
in TAction action)
|
||||
{
|
||||
this.batchHeight = batchHeight;
|
||||
this.memory = memory;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </summary>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
int lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(ref rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static System.Math;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper to validate arithmetic operations for <see cref="Memory2D{T}"/> and <see cref="Span2D{T}"/>.
|
||||
/// </summary>
|
||||
internal static class OverflowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the input parameters will not exceed the maximum native int value when indexing.
|
||||
/// </summary>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
|
||||
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
|
||||
/// <remarks>The input parameters are assumed to always be positive.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void EnsureIsInNativeIntRange(int height, int width, int pitch)
|
||||
{
|
||||
// As per the layout used in the Memory2D<T> and Span2D<T> types, we have the
|
||||
// following memory representation with respect to height, width and pitch:
|
||||
//
|
||||
// _________width_________ ________...
|
||||
// / \/
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |_
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height
|
||||
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_|
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
// ...__pitch__/
|
||||
//
|
||||
// The indexing logic works on nint values in unchecked mode, with no overflow checks,
|
||||
// which means it relies on the maximum element index to always be within <= nint.MaxValue.
|
||||
// To ensure no overflows will ever occur there, we need to ensure that no instance can be
|
||||
// created with parameters that could cause an overflow in case any item was accessed, so we
|
||||
// need to ensure no overflows occurs when calculating the index of the last item in each view.
|
||||
// The logic below calculates that index with overflow checks, throwing if one is detected.
|
||||
// Note that we're subtracting 1 to the height as we don't want to include the trailing pitch
|
||||
// for the 2D memory area, and also 1 to the width as the index is 0-based, as usual.
|
||||
// Additionally, we're also ensuring that the stride is never greater than int.MaxValue, for
|
||||
// consistency with how ND arrays work (int.MaxValue as upper bound for each axis), and to
|
||||
// allow for faster iteration in the RefEnumerable<T> type, when traversing columns.
|
||||
_ = checked(((nint)(width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the input parameters will not exceed <see cref="int.MaxValue"/> when indexing.
|
||||
/// </summary>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
|
||||
/// <returns>The area resulting from the given parameters.</returns>
|
||||
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
|
||||
/// <remarks>The input parameters are assumed to always be positive.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ComputeInt32Area(int height, int width, int pitch)
|
||||
{
|
||||
return checked(((width + pitch) * Max(unchecked(height - 1), 0)) + width);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class to throw exceptions for memory types.
|
||||
/// </summary>
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using the <see langword="void"/>* constructor with a managed type.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForManagedType()
|
||||
{
|
||||
throw new ArgumentException("Can't use a void* constructor when T is a managed type");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForDestinationTooShort()
|
||||
{
|
||||
throw new ArgumentException("The target span is too short to copy all the current items to");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the target span does not have the same shape as the source.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForDestinationWithNotSameShape()
|
||||
{
|
||||
throw new ArgumentException("The target span does not have the same shape as the source one");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArrayTypeMismatchException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
public static void ThrowArrayTypeMismatchException()
|
||||
{
|
||||
throw new ArrayTypeMismatchException("The given array doesn't match the specified type T");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when using an array of an invalid type.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentExceptionForUnsupportedType()
|
||||
{
|
||||
throw new ArgumentException("The specified object type is not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="IndexOutOfRangeException"/> when the a given coordinate is invalid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Throwing <see cref="IndexOutOfRangeException"/> is technically discouraged in the docs, but
|
||||
/// we're doing that here for consistency with the official <see cref="Span{T}"/> type(s) from the BCL.
|
||||
/// </remarks>
|
||||
public static void ThrowIndexOutOfRangeException()
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when more than one parameter are invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentException()
|
||||
{
|
||||
throw new ArgumentException("One or more input parameters were invalid");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "depth" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForDepth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("depth");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "row" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("row");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "column" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("column");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "offset" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForOffset()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("offset");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "height" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForHeight()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("height");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "width" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForWidth()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("width");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "pitch" parameter is invalid.
|
||||
/// </summary>
|
||||
public static void ThrowArgumentOutOfRangeExceptionForPitch()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("pitch");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,905 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Views;
|
||||
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
#pragma warning disable CA2231
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Memory2D{T}"/> represents a 2D region of arbitrary memory. It is to <see cref="Span2D{T}"/>
|
||||
/// what <see cref="Memory{T}"/> is to <see cref="Span{T}"/>. For further details on how the internal layout
|
||||
/// is structured, see the docs for <see cref="Span2D{T}"/>. The <see cref="Memory2D{T}"/> type can wrap arrays
|
||||
/// of any rank, provided that a valid series of parameters for the target memory area(s) are specified.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current <see cref="Memory2D{T}"/> instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public readonly struct Memory2D<T> : IEquatable<Memory2D<T>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int pitch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
|
||||
public Memory2D(T[] array, int height, int width)
|
||||
: this(array, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[] array, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)offset > (uint)array.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = array.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = GetArray2DDataByteOffset<T>();
|
||||
this.height = array.GetLength(0);
|
||||
this.width = array.GetLength(1);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public Memory2D(T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
if (row != 0 || column != 0 || height != 0 || width != 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(0),
|
||||
columns = array.GetLength(1);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public Memory2D(T[,,] array, int depth)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, 0, 0));
|
||||
this.height = array.GetLength(1);
|
||||
this.width = array.GetLength(2);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArrayTypeMismatchException">
|
||||
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public Memory2D(T[,,] array, int depth, int row, int column, int height, int width)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowHelper.ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(1),
|
||||
columns = array.GetLength(2);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
|
||||
public Memory2D(MemoryManager<T> memoryManager, int height, int width)
|
||||
: this(memoryManager, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
|
||||
/// </exception>
|
||||
public Memory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
|
||||
{
|
||||
int length = memoryManager.GetSpan().Length;
|
||||
|
||||
if ((uint)offset > (uint)length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
|
||||
internal Memory2D(Memory<T> memory, int height, int width)
|
||||
: this(memory, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
internal Memory2D(Memory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)memory.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = memory.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
// Check if the Memory<T> instance wraps a string. This is possible in case
|
||||
// consumers do an unsafe cast for the entire Memory<T> object, and while not
|
||||
// really safe it is still supported in CoreCLR too, so we're following suit here.
|
||||
if (typeof(T) == typeof(char) &&
|
||||
MemoryMarshal.TryGetString(Unsafe.As<Memory<T>, Memory<char>>(ref memory), out string? text, out int textStart, out _))
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
|
||||
|
||||
this.instance = text;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(text, ref r0);
|
||||
}
|
||||
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
|
||||
{
|
||||
// Check if the input Memory<T> instance wraps an array we can access.
|
||||
// This is fine, since Memory<T> on its own doesn't control the lifetime
|
||||
// of the underlying array anyway, and this Memory2D<T> type would do the same.
|
||||
// Using the array directly makes retrieving a Span2D<T> faster down the line,
|
||||
// as we no longer have to jump through the boxed Memory<T> first anymore.
|
||||
T[] array = segment.Array!;
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(segment.Offset + offset));
|
||||
}
|
||||
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
|
||||
{
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)(memoryManagerStart + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
|
||||
|
||||
this.instance = null;
|
||||
this.offset = default;
|
||||
}
|
||||
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Memory2D(object instance, IntPtr offset, int height, int width, int pitch)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Memory2D{T}"/> instance from an arbitrary object.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
|
||||
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
/// <returns>A <see cref="Memory2D{T}"/> instance with the specified parameters.</returns>
|
||||
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsibility of the caller to ensure it's valid.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
public static Memory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
|
||||
{
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
|
||||
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(instance, ref value);
|
||||
|
||||
return new Memory2D<T>(instance, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="Memory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public static Memory2D<T> Empty => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current <see cref="Memory2D{T}"/> instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height == 0 || this.width == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the current <see cref="Memory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public nint Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (nint)(uint)this.height * (nint)(uint)this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span2D{T}"/> instance from the current memory.
|
||||
/// </summary>
|
||||
public Span2D<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
|
||||
|
||||
return new Span2D<T>(ref r1, this.height, this.width, this.pitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset);
|
||||
|
||||
return new Span2D<T>(ref r0, this.height, this.width, this.pitch);
|
||||
}
|
||||
#else
|
||||
return new Span2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
|
||||
#endif
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="rows">The target range of rows to select.</param>
|
||||
/// <param name="columns">The target range of columns to select.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
public Memory2D<T> this[Range rows, Range columns]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var (row, height) = rows.GetOffsetAndLength(this.height);
|
||||
var (column, width) = columns.GetOffsetAndLength(this.width);
|
||||
|
||||
return Slice(row, column, height, width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to map within the current instance.</param>
|
||||
/// <param name="column">The target column to map within the current instance.</param>
|
||||
/// <param name="height">The height to map within the current instance.</param>
|
||||
/// <param name="width">The width to map within the current instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for the current instance.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
[Pure]
|
||||
public Memory2D<T> Slice(int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= this.width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (Height - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (this.width - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
int
|
||||
shift = ((this.width + this.pitch) * row) + column,
|
||||
pitch = this.pitch + (this.width - width);
|
||||
|
||||
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
|
||||
|
||||
return new Memory2D<T>(this.instance!, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handle for the memory.
|
||||
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
|
||||
/// is disposed, enabling taking and using the memory's address.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
|
||||
public unsafe MemoryHandle Pin()
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
return memoryManager.Pin();
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
|
||||
void* pointer = Unsafe.AsPointer(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset));
|
||||
|
||||
return new MemoryHandle(pointer, handle);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="Memory{T}"/> instance, if the underlying buffer is contiguous and small enough.
|
||||
/// </summary>
|
||||
/// <param name="memory">The resulting <see cref="Memory{T}"/>, in case of success.</param>
|
||||
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
|
||||
public bool TryGetMemory(out Memory<T> memory)
|
||||
{
|
||||
if (this.pitch == 0 &&
|
||||
Length <= int.MaxValue)
|
||||
{
|
||||
// Empty Memory2D<T> instance
|
||||
if (this.instance is null)
|
||||
{
|
||||
memory = default;
|
||||
}
|
||||
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
|
||||
{
|
||||
string text = Unsafe.As<string>(this.instance)!;
|
||||
int index = text.AsSpan().IndexOf(in ObjectMarshal.DangerousGetObjectDataReferenceAt<char>(text, this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
// The string type could still be present if a user ends up creating a
|
||||
// Memory2D<T> instance from a string using DangerousCreate. Similarly to
|
||||
// how CoreCLR handles the equivalent case in Memory<T>, here we just do
|
||||
// the necessary steps to still retrieve a Memory<T> instance correctly
|
||||
// wrapping the target string. In this case, it is up to the caller
|
||||
// to make sure not to ever actually write to the resulting Memory<T>.
|
||||
memory = MemoryMarshal.AsMemory<T>(Unsafe.As<ReadOnlyMemory<char>, Memory<T>>(ref temp));
|
||||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance)!;
|
||||
int index = array.AsSpan().IndexOf(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
}
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
else if (this.instance.GetType() == typeof(T[,]) ||
|
||||
this.instance.GetType() == typeof(T[,,]))
|
||||
{
|
||||
// If the object is a 2D or 3D array, we can create a Memory<T> from the RawObjectMemoryManager<T> type.
|
||||
// We just need to use the precomputed offset pointing to the first item in the current instance,
|
||||
// and the current usable length. We don't need to retrieve the current index, as the manager just offsets.
|
||||
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Reuse a single failure path to reduce
|
||||
// the number of returns in the method
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Failure:
|
||||
|
||||
memory = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the current <see cref="Memory2D{T}"/> instance into a new 2D array.
|
||||
/// </summary>
|
||||
/// <returns>A 2D array containing the data in the current <see cref="Memory2D{T}"/> instance.</returns>
|
||||
[Pure]
|
||||
public T[,] ToArray() => Span.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Memory2D<T> memory)
|
||||
{
|
||||
return Equals(memory);
|
||||
}
|
||||
|
||||
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
|
||||
{
|
||||
return readOnlyMemory.Equals(this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Memory2D<T> other)
|
||||
{
|
||||
return
|
||||
this.instance == other.instance &&
|
||||
this.offset == other.offset &&
|
||||
this.height == other.height &&
|
||||
this.width == other.width &&
|
||||
this.pitch == other.pitch;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
#if !NETSTANDARD1_4
|
||||
return HashCode.Combine(
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset,
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch);
|
||||
#else
|
||||
Span<int> values = stackalloc int[]
|
||||
{
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset.GetHashCode(),
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch
|
||||
};
|
||||
|
||||
return values.GetDjb2HashCode();
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Microsoft.Toolkit.HighPerformance.Memory2D<{typeof(T)}>[{this.height}, {this.width}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of an array to a <see cref="Memory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator Memory2D<T>(T[,]? array) => new(array);
|
||||
}
|
||||
}
|
|
@ -1,923 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Views;
|
||||
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
#pragma warning disable CA2231
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <summary>
|
||||
/// A readonly version of <see cref="Memory2D{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</typeparam>
|
||||
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The pitch of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int pitch;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="text">The target <see cref="string"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="text"/>.</remarks>
|
||||
public ReadOnlyMemory2D(string text, int height, int width)
|
||||
: this(text, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="text">The target <see cref="string"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="text"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="text"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)text.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = text.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = text;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(text, ref text.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
|
||||
public ReadOnlyMemory2D(T[] array, int height, int width)
|
||||
: this(array, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="array">The target array to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)array.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = array.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(offset));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
public ReadOnlyMemory2D(T[,]? array)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = GetArray2DDataByteOffset<T>();
|
||||
this.height = array.GetLength(0);
|
||||
this.width = array.GetLength(1);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 2D array to wrap.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width)
|
||||
{
|
||||
if (array is null)
|
||||
{
|
||||
if (row != 0 || column != 0 || height != 0 || width != 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(0),
|
||||
columns = array.GetLength(1);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public ReadOnlyMemory2D(T[,,] array, int depth)
|
||||
{
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, 0, 0));
|
||||
this.height = array.GetLength(1);
|
||||
this.width = array.GetLength(2);
|
||||
this.pitch = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
|
||||
/// </summary>
|
||||
/// <param name="array">The given 3D array to wrap.</param>
|
||||
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
|
||||
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
|
||||
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
|
||||
/// <param name="height">The height to map within <paramref name="array"/>.</param>
|
||||
/// <param name="width">The width to map within <paramref name="array"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
|
||||
public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)depth >= (uint)array.GetLength(0))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
|
||||
}
|
||||
|
||||
int
|
||||
rows = array.GetLength(1),
|
||||
columns = array.GetLength(2);
|
||||
|
||||
if ((uint)row >= (uint)rows)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= (uint)columns)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (uint)(rows - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (uint)(columns - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, row, column));
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = columns - width;
|
||||
}
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
|
||||
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width)
|
||||
: this(memoryManager, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
|
||||
/// </exception>
|
||||
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
|
||||
{
|
||||
int length = memoryManager.GetSpan().Length;
|
||||
|
||||
if ((uint)offset > (uint)length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
|
||||
/// </exception>
|
||||
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
|
||||
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int height, int width)
|
||||
: this(memory, 0, height, width, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="memory">The target <see cref="ReadOnlyMemory{T}"/> to wrap.</param>
|
||||
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
|
||||
/// <param name="height">The height of the resulting 2D area.</param>
|
||||
/// <param name="width">The width of each row in the resulting 2D area.</param>
|
||||
/// <param name="pitch">The pitch in the resulting 2D area.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
|
||||
/// </exception>
|
||||
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
|
||||
{
|
||||
if ((uint)offset > (uint)memory.Length)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
this = default;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int
|
||||
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
|
||||
remaining = memory.Length - offset;
|
||||
|
||||
if (area > remaining)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException();
|
||||
}
|
||||
|
||||
// Check the supported underlying objects, like in Memory2D<T>
|
||||
if (typeof(T) == typeof(char) &&
|
||||
MemoryMarshal.TryGetString(Unsafe.As<ReadOnlyMemory<T>, ReadOnlyMemory<char>>(ref memory), out string? text, out int textStart, out _))
|
||||
{
|
||||
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
|
||||
|
||||
this.instance = text;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(text, ref r0);
|
||||
}
|
||||
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
|
||||
{
|
||||
T[] array = segment.Array!;
|
||||
|
||||
this.instance = array;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(segment.Offset + offset));
|
||||
}
|
||||
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
|
||||
{
|
||||
this.instance = memoryManager;
|
||||
this.offset = (nint)(uint)(memoryManagerStart + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
|
||||
|
||||
this.instance = null;
|
||||
this.offset = default;
|
||||
}
|
||||
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="instance">The target <see cref="object"/> instance.</param>
|
||||
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.offset = offset;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ReadOnlyMemory2D{T}"/> instance from an arbitrary object.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
|
||||
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
|
||||
/// <param name="height">The height of the 2D memory area to map.</param>
|
||||
/// <param name="width">The width of the 2D memory area to map.</param>
|
||||
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
|
||||
/// <returns>A <see cref="ReadOnlyMemory2D{T}"/> instance with the specified parameters.</returns>
|
||||
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsibility of the caller to ensure it's valid.</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when one of the input parameters is out of range.
|
||||
/// </exception>
|
||||
[Pure]
|
||||
public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
|
||||
{
|
||||
if (height < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
if (pitch < 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
|
||||
}
|
||||
|
||||
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
|
||||
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(instance, ref value);
|
||||
|
||||
return new ReadOnlyMemory2D<T>(instance, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public static ReadOnlyMemory2D<T> Empty => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current <see cref="ReadOnlyMemory2D{T}"/> instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height == 0 || this.width == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the current <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public nint Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (nint)(uint)this.height * (nint)(uint)this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the underlying 2D memory area.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ReadOnlySpan2D{T}"/> instance from the current memory.
|
||||
/// </summary>
|
||||
public ReadOnlySpan2D<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
|
||||
|
||||
return new ReadOnlySpan2D<T>(in r1, this.height, this.width, this.pitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This handles both arrays and strings
|
||||
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset);
|
||||
|
||||
return new ReadOnlySpan2D<T>(in r0, this.height, this.width, this.pitch);
|
||||
}
|
||||
#else
|
||||
return new ReadOnlySpan2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
|
||||
#endif
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="rows">The target range of rows to select.</param>
|
||||
/// <param name="columns">The target range of columns to select.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
public ReadOnlyMemory2D<T> this[Range rows, Range columns]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
var (row, height) = rows.GetOffsetAndLength(this.height);
|
||||
var (column, width) = columns.GetOffsetAndLength(this.width);
|
||||
|
||||
return Slice(row, column, height, width);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Slices the current instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to map within the current instance.</param>
|
||||
/// <param name="column">The target column to map within the current instance.</param>
|
||||
/// <param name="height">The height to map within the current instance.</param>
|
||||
/// <param name="width">The width to map within the current instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
|
||||
/// are negative or not within the bounds that are valid for the current instance.
|
||||
/// </exception>
|
||||
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
|
||||
[Pure]
|
||||
public ReadOnlyMemory2D<T> Slice(int row, int column, int height, int width)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
if ((uint)column >= this.width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
if ((uint)height > (Height - row))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
|
||||
}
|
||||
|
||||
if ((uint)width > (this.width - column))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
|
||||
}
|
||||
|
||||
int
|
||||
shift = ((this.width + this.pitch) * row) + column,
|
||||
pitch = this.pitch + (this.width - width);
|
||||
|
||||
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
|
||||
|
||||
return new ReadOnlyMemory2D<T>(this.instance!, offset, height, width, pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
|
||||
/// </exception>
|
||||
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
|
||||
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
|
||||
/// </summary>
|
||||
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
|
||||
/// <returns>Whether or not the operation was successful.</returns>
|
||||
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handle for the memory.
|
||||
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
|
||||
/// is disposed, enabling taking and using the memory's address.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
|
||||
/// </exception>
|
||||
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
|
||||
public unsafe MemoryHandle Pin()
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
return memoryManager.Pin();
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
|
||||
|
||||
void* pointer = Unsafe.AsPointer(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset));
|
||||
|
||||
return new MemoryHandle(pointer, handle);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="ReadOnlyMemory{T}"/> instance, if the underlying buffer is contiguous and small enough.
|
||||
/// </summary>
|
||||
/// <param name="memory">The resulting <see cref="ReadOnlyMemory{T}"/>, in case of success.</param>
|
||||
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
|
||||
public bool TryGetMemory(out ReadOnlyMemory<T> memory)
|
||||
{
|
||||
if (this.pitch == 0 &&
|
||||
Length <= int.MaxValue)
|
||||
{
|
||||
// Empty Memory2D<T> instance
|
||||
if (this.instance is null)
|
||||
{
|
||||
memory = default;
|
||||
}
|
||||
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
|
||||
{
|
||||
// Here we need to create a Memory<char> from the wrapped string, and to do so we need to do an inverse
|
||||
// lookup to find the initial index of the string with respect to the byte offset we're currently using,
|
||||
// which refers to the raw string object data. This can include variable padding or other additional
|
||||
// fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset
|
||||
// difference between the start of the Span<char> (which directly wraps just the actual character data
|
||||
// within the string), and the input reference, which we can get from the byte offset in use. The result
|
||||
// is the character index which we can use to create the final Memory<char> instance.
|
||||
string text = Unsafe.As<string>(this.instance)!;
|
||||
int index = text.AsSpan().IndexOf(in ObjectMarshal.DangerousGetObjectDataReferenceAt<char>(text, this.offset));
|
||||
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
|
||||
|
||||
memory = Unsafe.As<ReadOnlyMemory<char>, ReadOnlyMemory<T>>(ref temp);
|
||||
}
|
||||
else if (this.instance is MemoryManager<T> memoryManager)
|
||||
{
|
||||
// If the object is a MemoryManager<T>, just slice it as needed
|
||||
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
|
||||
}
|
||||
else if (this.instance.GetType() == typeof(T[]))
|
||||
{
|
||||
// If it's a T[] array, also handle the initial offset
|
||||
T[] array = Unsafe.As<T[]>(this.instance)!;
|
||||
int index = array.AsSpan().IndexOf(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, this.offset));
|
||||
|
||||
memory = array.AsMemory(index, this.height * this.width);
|
||||
}
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
else if (this.instance.GetType() == typeof(T[,]) ||
|
||||
this.instance.GetType() == typeof(T[,,]))
|
||||
{
|
||||
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Reuse a single failure path to reduce
|
||||
// the number of returns in the method
|
||||
goto Failure;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Failure:
|
||||
|
||||
memory = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the current <see cref="ReadOnlyMemory2D{T}"/> instance into a new 2D array.
|
||||
/// </summary>
|
||||
/// <returns>A 2D array containing the data in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
|
||||
[Pure]
|
||||
public T[,] ToArray() => Span.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
|
||||
{
|
||||
return Equals(readOnlyMemory);
|
||||
}
|
||||
|
||||
if (obj is Memory2D<T> memory)
|
||||
{
|
||||
return Equals(memory);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ReadOnlyMemory2D<T> other)
|
||||
{
|
||||
return
|
||||
this.instance == other.instance &&
|
||||
this.offset == other.offset &&
|
||||
this.height == other.height &&
|
||||
this.width == other.width &&
|
||||
this.pitch == other.pitch;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (this.instance is not null)
|
||||
{
|
||||
#if !NETSTANDARD1_4
|
||||
return HashCode.Combine(
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset,
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch);
|
||||
#else
|
||||
Span<int> values = stackalloc int[]
|
||||
{
|
||||
RuntimeHelpers.GetHashCode(this.instance),
|
||||
this.offset.GetHashCode(),
|
||||
this.height,
|
||||
this.width,
|
||||
this.pitch
|
||||
};
|
||||
|
||||
return values.GetDjb2HashCode();
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Microsoft.Toolkit.HighPerformance.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new(array);
|
||||
|
||||
/// <summary>
|
||||
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>
|
||||
/// </summary>
|
||||
public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory) => Unsafe.As<Memory2D<T>, ReadOnlyMemory2D<T>>(ref memory);
|
||||
}
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <inheritdoc cref="ReadOnlySpan2D{T}"/>
|
||||
public readonly ref partial struct ReadOnlySpan2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified row.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyRefEnumerable<T> GetRow(int row)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
nint startIndex = (nint)(uint)this.stride * (nint)(uint)row;
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(in r1, Width, 1);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
|
||||
|
||||
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, this.width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified column.
|
||||
/// </summary>
|
||||
/// <param name="column">The target column to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyRefEnumerable<T> GetColumn(int column)
|
||||
{
|
||||
if ((uint)column >= Width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new ReadOnlyRefEnumerable<T>(in r1, Height, this.stride);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
|
||||
|
||||
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, Height, this.stride);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator for the current <see cref="ReadOnlySpan2D{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to traverse the items in the current <see cref="ReadOnlySpan2D{T}"/> instance
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
|
||||
/// <summary>
|
||||
/// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>Just like in <see cref="ReadOnlySpan2D{T}"/>, the length is the height of the 2D region.</remarks>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The stride of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int stride;
|
||||
|
||||
/// <summary>
|
||||
/// The current horizontal offset.
|
||||
/// </summary>
|
||||
private int x;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset.
|
||||
/// </summary>
|
||||
private int y;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The target <see cref="ReadOnlySpan2D{T}"/> instance to enumerate.</param>
|
||||
internal Enumerator(ReadOnlySpan2D<T> span)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = span.span;
|
||||
#else
|
||||
this.instance = span.instance;
|
||||
this.offset = span.offset;
|
||||
this.height = span.height;
|
||||
#endif
|
||||
this.width = span.width;
|
||||
this.stride = span.stride;
|
||||
this.x = -1;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int x = this.x + 1;
|
||||
|
||||
// Horizontal move, within range
|
||||
if (x < this.width)
|
||||
{
|
||||
this.x = x;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We reached the end of a row and there is at least
|
||||
// another row available: wrap to a new line and continue.
|
||||
this.x = 0;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.y < this.span.Length;
|
||||
#else
|
||||
return ++this.y < this.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly ref readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
|
||||
|
||||
return ref Unsafe.Add(ref r0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,201 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance
|
||||
{
|
||||
/// <inheritdoc cref="Span2D{T}"/>
|
||||
public readonly ref partial struct Span2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified row.
|
||||
/// </summary>
|
||||
/// <param name="row">The target row to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefEnumerable<T> GetRow(int row)
|
||||
{
|
||||
if ((uint)row >= Height)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
nint startIndex = (nint)(uint)this.Stride * (nint)(uint)row;
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new RefEnumerable<T>(ref r1, Width, 1);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
|
||||
|
||||
return new RefEnumerable<T>(this.Instance, offset, this.width, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerable that traverses items in a specified column.
|
||||
/// </summary>
|
||||
/// <param name="column">The target column to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
|
||||
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
|
||||
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public RefEnumerable<T> GetColumn(int column)
|
||||
{
|
||||
if ((uint)column >= Width)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
ref T r0 = ref DangerousGetReference();
|
||||
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return new RefEnumerable<T>(ref r1, Height, this.Stride);
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
|
||||
|
||||
return new RefEnumerable<T>(this.Instance, offset, Height, this.Stride);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator for the current <see cref="Span2D{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to traverse the items in the current <see cref="Span2D{T}"/> instance
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
|
||||
/// <summary>
|
||||
/// Provides an enumerator for the elements of a <see cref="Span2D{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
|
||||
/// </summary>
|
||||
/// <remarks>Just like in <see cref="Span2D{T}"/>, the length is the height of the 2D region.</remarks>
|
||||
private readonly Span<T> span;
|
||||
#else
|
||||
/// <summary>
|
||||
/// The target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int height;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The width of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int width;
|
||||
|
||||
/// <summary>
|
||||
/// The stride of the specified 2D region.
|
||||
/// </summary>
|
||||
private readonly int stride;
|
||||
|
||||
/// <summary>
|
||||
/// The current horizontal offset.
|
||||
/// </summary>
|
||||
private int x;
|
||||
|
||||
/// <summary>
|
||||
/// The current vertical offset.
|
||||
/// </summary>
|
||||
private int y;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The target <see cref="Span2D{T}"/> instance to enumerate.</param>
|
||||
internal Enumerator(Span2D<T> span)
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
this.span = span.span;
|
||||
#else
|
||||
this.instance = span.Instance;
|
||||
this.offset = span.Offset;
|
||||
this.height = span.height;
|
||||
#endif
|
||||
this.width = span.width;
|
||||
this.stride = span.Stride;
|
||||
this.x = -1;
|
||||
this.y = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
int x = this.x + 1;
|
||||
|
||||
// Horizontal move, within range
|
||||
if (x < this.width)
|
||||
{
|
||||
this.x = x;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We reached the end of a row and there is at least
|
||||
// another row available: wrap to a new line and continue.
|
||||
this.x = 0;
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
return ++this.y < this.span.Length;
|
||||
#else
|
||||
return ++this.y < this.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public readonly ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
#endif
|
||||
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
|
||||
|
||||
return ref Unsafe.Add(ref r0, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,57 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Memory.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A debug proxy used to display items in a 2D layout.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to display.</typeparam>
|
||||
internal sealed class MemoryDebugView2D<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="Memory2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(Memory2D<T> memory)
|
||||
{
|
||||
this.Items = memory.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(ReadOnlyMemory2D<T> memory)
|
||||
{
|
||||
this.Items = memory.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="Span2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(Span2D<T> span)
|
||||
{
|
||||
this.Items = span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="span">The input <see cref="ReadOnlySpan2D{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView2D(ReadOnlySpan2D<T> span)
|
||||
{
|
||||
this.Items = span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items to display for the current instance
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
|
||||
public T[,]? Items { get; }
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>Enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1;net5.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Title>Windows Community Toolkit - High Performance (.NET Standard)</Title>
|
||||
<Description>
|
||||
This package includes high performance .NET Standard helpers such as:
|
||||
- Memory2D<T> and Span2D<T>: two types providing fast and allocation-free abstraction over 2D memory areas.
|
||||
- ArrayPoolBufferWriter<T>: an IBufferWriter<T> implementation using pooled arrays, which also supports IMemoryOwner<T>.
|
||||
- MemoryBufferWriter<T>: an IBufferWriter<T>: implementation that can wrap external Memory<T>: instances.
|
||||
- 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.
|
||||
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
|
||||
- String, array, Memory<T>, Span<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.
|
||||
- NullableRef<T>: a stack-only struct similar to Ref<T>, which also supports nullable references.
|
||||
</Description>
|
||||
<PackageTags>Parallel;Performance;Unsafe;Span;Memory;String;StringPool;Array;Stream;Buffer;Extensions;Helpers</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard1.4'">
|
||||
<!--
|
||||
.NET Standard 1.4 lacks the [Pure] attribute, the Rectangle primitive,
|
||||
the Span<T> and Memory<T> types, the Vector<T> primitive and related APIs,
|
||||
ValueTask and ValueTask<T>, the Parallel class and the Unsafe class.
|
||||
We also need to reference the System.Runtime.CompilerServices.Unsafe package directly,
|
||||
even though System.Memory references it already, as we need a more recent version than
|
||||
the one bundled with it. This is so that we can use the Unsafe.Unbox<T> method,
|
||||
which is used by the Box<T> type in the package.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
|
||||
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<!-- .NET Standard 2.0 doesn't have the Span<T>, HashCode and ValueTask types -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<!--
|
||||
NETSTANDARD2_1_OR_GREATER: includes both .NET Standard 2.1 and .NET Core 3.1.
|
||||
This is needed because .NET Core 3.1 will be a separate package than .NET Standard 2.1.
|
||||
|
||||
SPAN_RUNTIME_SUPPORT: define a constant to indicate runtimes with runtime support for
|
||||
the fast Span<T> type, as well as some overloads with Span<T> parameters (eg. Stream.Write).
|
||||
In particular, these are runtimes which are able to create Span<T> instances from just
|
||||
a managed reference, which can be used to slice arbitrary objects not technically supported.
|
||||
This API (MemoryMarshal.CreateSpan) is not part of .NET Standard 2.0, but it is still
|
||||
available on .NET Core 2.1. So by using this constant, we can make sure to expose those
|
||||
APIs relying on that method on all target frameworks that are able to support them.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PropertyGroup>
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<!--
|
||||
NETCORE_RUNTIME: to avoid issues with APIs that assume a specific memory layout, we define a
|
||||
.NET Core runtime constant to indicate either .NET Core 2.1 or .NET Core 3.1. These are
|
||||
runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays,
|
||||
and ND arrays). We can use this constant to make sure that APIs that are exclusively available
|
||||
for .NET Standard targets do not make any assumtpion of any internals of the runtime being
|
||||
actually used by the consumers. .NET 5.0 would fall into this category as well, but we don't
|
||||
need to include that target as it offers APIs that don't require runtime-based workarounds.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
|
||||
<When Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
|
||||
<PropertyGroup>
|
||||
<DefineConstants>SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
|
||||
</Project>
|
|
@ -1,139 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.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>
|
||||
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);
|
||||
|
||||
this.span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NullableReadOnlyRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance to track the target <typeparamref name="T"/> reference.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private NullableReadOnlyRef(ReadOnlySpan<T> span)
|
||||
{
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="NullableReadOnlyRef{T}"/> instance representing a <see langword="null"/> reference.
|
||||
/// </summary>
|
||||
public static NullableReadOnlyRef<T> Null
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
/// <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 unsafe bool HasValue
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
// See comment in NullableRef<T> about this
|
||||
byte length = unchecked((byte)this.span.Length);
|
||||
|
||||
return *(bool*)&length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableReadOnlyRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (!HasValue)
|
||||
{
|
||||
ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
return ref MemoryMarshal.GetReference(this.span);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(Ref<T> reference)
|
||||
{
|
||||
return new(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="ReadOnlyRef{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="ReadOnlyRef{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(ReadOnlyRef<T> reference)
|
||||
{
|
||||
return new(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="NullableRef{T}"/> instance into a <see cref="NullableReadOnlyRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableReadOnlyRef<T>(NullableRef<T> reference)
|
||||
{
|
||||
return new(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly gets the <typeparamref name="T"/> value from a given <see cref="NullableReadOnlyRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="NullableReadOnlyRef{T}"/> instance.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator T(NullableReadOnlyRef<T> reference)
|
||||
{
|
||||
return reference.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,123 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.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>
|
||||
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>
|
||||
internal 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>
|
||||
/// Initializes a new instance of the <see cref="NullableRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The <see cref="Span{T}"/> instance to track the target <typeparamref name="T"/> reference.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private NullableRef(Span<T> span)
|
||||
{
|
||||
Span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="NullableRef{T}"/> instance representing a <see langword="null"/> reference.
|
||||
/// </summary>
|
||||
public static NullableRef<T> Null
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
/// <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 unsafe bool HasValue
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
// We know that the span will always have a length of either
|
||||
// 1 or 0, so 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 *(bool*)&length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="NullableRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
public ref T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (!HasValue)
|
||||
{
|
||||
ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
return ref MemoryMarshal.GetReference(Span);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="Ref{T}"/> instance into a <see cref="NullableRef{T}"/> one.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="Ref{T}"/> instance.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator NullableRef<T>(Ref<T> reference)
|
||||
{
|
||||
return new(reference.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly gets the <typeparamref name="T"/> value from a given <see cref="NullableRef{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="reference">The input <see cref="NullableRef{T}"/> instance.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if <see cref="HasValue"/> is <see langword="false"/>.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static explicit operator T(NullableRef<T> reference)
|
||||
{
|
||||
return reference.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,11 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// We can suppress the .init flag for local variables for the entire module.
|
||||
// This doesn't affect the correctness of methods in this assembly, as none of them
|
||||
// are relying on the JIT ensuring that all local memory is zeroed out to work. Doing
|
||||
// this can provide some minor performance benefits, depending on the workload.
|
||||
[module: SkipLocalsInit]
|
|
@ -1,134 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#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>
|
||||
public readonly ref struct ReadOnlyRef<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The 1-length <see cref="ReadOnlySpan{T}"/> instance used to track the target <typeparamref name="T"/> value.
|
||||
/// </summary>
|
||||
internal 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>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="pointer">The pointer to the target value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ReadOnlyRef(void* pointer)
|
||||
: this(in Unsafe.AsRef<T>(pointer))
|
||||
{
|
||||
}
|
||||
|
||||
/// <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(in 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 responsibility 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 responsibility of the caller to ensure it's valid.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyRef(object owner, in T value)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(owner, ref Unsafe.AsRef(value));
|
||||
}
|
||||
|
||||
/// <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 ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.owner, this.offset);
|
||||
}
|
||||
|
||||
/// <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(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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers;
|
||||
#endif
|
||||
|
||||
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>
|
||||
public readonly ref struct Ref<T>
|
||||
{
|
||||
#if SPAN_RUNTIME_SUPPORT
|
||||
/// <summary>
|
||||
/// The 1-length <see cref="Span{T}"/> instance used to track the target <typeparamref name="T"/> value.
|
||||
/// </summary>
|
||||
internal 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>
|
||||
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="pointer">The pointer to the target value.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe Ref(void* pointer)
|
||||
: this(ref Unsafe.AsRef<T>(pointer))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
|
||||
/// </summary>
|
||||
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 responsibility of the caller to ensure it's valid.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Ref(object owner, ref T value)
|
||||
{
|
||||
Owner = owner;
|
||||
Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(owner, ref value);
|
||||
}
|
||||
|
||||
/// <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 ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(Owner, Offset);
|
||||
}
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче