Switch whole solution to file-scoped namespaces
This commit is contained in:
Родитель
32f3f7ce3c
Коммит
c176080d37
|
@ -387,3 +387,4 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:war
|
|||
csharp_prefer_static_local_function = true:warning
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
|
|
@ -4,26 +4,25 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
namespace System.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that the output will be non-null if the named parameter is non-null.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class NotNullIfNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that the output will be non-null if the named parameter is non-null.
|
||||
/// Initializes a new instance of the <see cref="NotNullIfNotNullAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class NotNullIfNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotNullIfNotNullAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.</param>
|
||||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
|
||||
/// <param name="parameterName">The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.</param>
|
||||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the associated parameter name.
|
||||
/// </summary>
|
||||
public string ParameterName { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the associated parameter name.
|
||||
/// </summary>
|
||||
public string ParameterName { get; }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -4,26 +4,25 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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 from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
|
||||
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
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 null.</param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the annotated variable is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the annotated variable is not <see langword="null"/>.
|
||||
/// </summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -4,24 +4,23 @@
|
|||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// An interface for a grouped collection of items.
|
||||
/// It allows us to use x:Bind with <see cref="ObservableGroup{TKey, TValue}"/> and <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> by providing
|
||||
/// a non-generic type that we can declare using x:DataType.
|
||||
/// </summary>
|
||||
public interface IReadOnlyObservableGroup : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for a grouped collection of items.
|
||||
/// It allows us to use x:Bind with <see cref="ObservableGroup{TKey, TValue}"/> and <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> by providing
|
||||
/// a non-generic type that we can declare using x:DataType.
|
||||
/// Gets the key for the current collection, as an <see cref="object"/>.
|
||||
/// It is immutable.
|
||||
/// </summary>
|
||||
public interface IReadOnlyObservableGroup : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the key for the current collection, as an <see cref="object"/>.
|
||||
/// It is immutable.
|
||||
/// </summary>
|
||||
object Key { get; }
|
||||
object Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items currently in the grouped collection.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the number of items currently in the grouped collection.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
}
|
||||
|
|
|
@ -8,73 +8,72 @@ using System.ComponentModel;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// An observable group.
|
||||
/// It associates a <see cref="Key"/> to an <see cref="ObservableCollection{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
[DebuggerDisplay("Key = {Key}, Count = {Count}")]
|
||||
public class ObservableGroup<TKey, TValue> : ObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// An observable group.
|
||||
/// It associates a <see cref="Key"/> to an <see cref="ObservableCollection{T}"/>.
|
||||
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="Key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
[DebuggerDisplay("Key = {Key}, Count = {Count}")]
|
||||
public class ObservableGroup<TKey, TValue> : ObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
|
||||
where TKey : notnull
|
||||
private static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(Key));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the group.</param>
|
||||
public ObservableGroup(TKey key)
|
||||
{
|
||||
/// <summary>
|
||||
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="Key"/>
|
||||
/// </summary>
|
||||
private static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(Key));
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the group.</param>
|
||||
public ObservableGroup(TKey key)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="grouping">The grouping to fill the group.</param>
|
||||
public ObservableGroup(IGrouping<TKey, TValue> grouping)
|
||||
: base(grouping)
|
||||
{
|
||||
this.key = grouping.Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the group.</param>
|
||||
/// <param name="collection">The initial collection of data to add to the group.</param>
|
||||
public ObservableGroup(TKey key, IEnumerable<TValue> collection)
|
||||
: base(collection)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
private TKey key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key of the group.
|
||||
/// </summary>
|
||||
public TKey Key
|
||||
{
|
||||
get => this.key;
|
||||
set
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="grouping">The grouping to fill the group.</param>
|
||||
public ObservableGroup(IGrouping<TKey, TValue> grouping)
|
||||
: base(grouping)
|
||||
{
|
||||
this.key = grouping.Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the group.</param>
|
||||
/// <param name="collection">The initial collection of data to add to the group.</param>
|
||||
public ObservableGroup(TKey key, IEnumerable<TValue> collection)
|
||||
: base(collection)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
private TKey key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key of the group.
|
||||
/// </summary>
|
||||
public TKey Key
|
||||
{
|
||||
get => this.key;
|
||||
set
|
||||
if (!EqualityComparer<TKey>.Default.Equals(this.key!, value))
|
||||
{
|
||||
if (!EqualityComparer<TKey>.Default.Equals(this.key!, value))
|
||||
{
|
||||
this.key = value;
|
||||
this.key = value;
|
||||
|
||||
OnPropertyChanged(KeyChangedEventArgs);
|
||||
}
|
||||
OnPropertyChanged(KeyChangedEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IReadOnlyObservableGroup.Key => Key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IReadOnlyObservableGroup.Key => Key;
|
||||
}
|
||||
|
|
|
@ -8,43 +8,42 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// An observable list of observable groups.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
public sealed class ObservableGroupedCollection<TKey, TValue> : ObservableCollection<ObservableGroup<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// An observable list of observable groups.
|
||||
/// Initializes a new instance of the <see cref="ObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
public sealed class ObservableGroupedCollection<TKey, TValue> : ObservableCollection<ObservableGroup<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
public ObservableGroupedCollection()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
public ObservableGroupedCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
|
||||
: base(collection.Select(static c => new ObservableGroup<TKey, TValue>(c)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the underlying <see cref="List{T}"/> instance, if present.
|
||||
/// </summary>
|
||||
/// <param name="list">The resulting <see cref="List{T}"/>, if one was in use.</param>
|
||||
/// <returns>Whether or not a <see cref="List{T}"/> instance has been found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool TryGetList([NotNullWhen(true)] out List<ObservableGroup<TKey, TValue>>? list)
|
||||
{
|
||||
list = Items as List<ObservableGroup<TKey, TValue>>;
|
||||
|
||||
return list is not null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
|
||||
: base(collection.Select(static c => new ObservableGroup<TKey, TValue>(c)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the underlying <see cref="List{T}"/> instance, if present.
|
||||
/// </summary>
|
||||
/// <param name="list">The resulting <see cref="List{T}"/>, if one was in use.</param>
|
||||
/// <returns>Whether or not a <see cref="List{T}"/> instance has been found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool TryGetList([NotNullWhen(true)] out List<ObservableGroup<TKey, TValue>>? list)
|
||||
{
|
||||
list = Items as List<ObservableGroup<TKey, TValue>>;
|
||||
|
||||
return list is not null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,326 +8,354 @@ using System.Diagnostics.Contracts;
|
|||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// The extensions methods to simplify the usage of <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
public static class ObservableGroupedCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The extensions methods to simplify the usage of <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// Return the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
public static class ObservableGroupedCollectionExtensions
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <returns>The first group matching <paramref name="key"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
[Pure]
|
||||
public static ObservableGroup<TKey, TValue> First<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <returns>The first group matching <paramref name="key"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
[Pure]
|
||||
public static ObservableGroup<TKey, TValue> First<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
where TKey : notnull
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null)
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null)
|
||||
static void ThrowArgumentExceptionForKeyNotFound()
|
||||
{
|
||||
static void ThrowArgumentExceptionForKeyNotFound()
|
||||
{
|
||||
throw new InvalidOperationException("The requested key was not present in the collection");
|
||||
}
|
||||
|
||||
ThrowArgumentExceptionForKeyNotFound();
|
||||
throw new InvalidOperationException("The requested key was not present in the collection");
|
||||
}
|
||||
|
||||
return group!;
|
||||
ThrowArgumentExceptionForKeyNotFound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the first group with <paramref name="key"/> key or null if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <returns>The first group matching <paramref name="key"/> or null.</returns>
|
||||
[Pure]
|
||||
public static ObservableGroup<TKey, TValue>? FirstOrDefault<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
{
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return group!;
|
||||
}
|
||||
|
||||
return null;
|
||||
/// <summary>
|
||||
/// Return the first group with <paramref name="key"/> key or null if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <returns>The first group matching <paramref name="key"/> or null.</returns>
|
||||
[Pure]
|
||||
public static ObservableGroup<TKey, TValue>? FirstOrDefault<TKey, TValue>(this ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
{
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static ObservableGroup<TKey, TValue>? FirstOrDefaultWithLinq(ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
{
|
||||
return source.FirstOrDefault(group => EqualityComparer<TKey>.Default.Equals(group.Key, key));
|
||||
}
|
||||
|
||||
return FirstOrDefaultWithLinq(source, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <param name="index">The index of the item from the targeted group.</param>
|
||||
/// <returns>The element.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
[Pure]
|
||||
public static TValue ElementAt<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index)
|
||||
where TKey : notnull
|
||||
=> source.First(key)[index];
|
||||
|
||||
/// <summary>
|
||||
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <param name="index">The index of the item from the targeted group.</param>
|
||||
/// <returns>The element or default(TValue) if it does not exist.</returns>
|
||||
[Pure]
|
||||
public static TValue? ElementAtOrDefault<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null ||
|
||||
(uint)index >= (uint)group.Count)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return group[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-value <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="value"/> will be added.</param>
|
||||
/// <param name="value">The value to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue value)
|
||||
where TKey : notnull
|
||||
=> AddGroup(source, key, new[] { value });
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
|
||||
/// <param name="collection">The collection to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
params TValue[] collection)
|
||||
where TKey : notnull
|
||||
=> source.AddGroup(key, (IEnumerable<TValue>)collection);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
|
||||
/// <param name="collection">The collection to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
IEnumerable<TValue> collection)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = new(key, collection);
|
||||
source.Add(group);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add <paramref name="item"/> into the first group with <paramref name="key"/> key.
|
||||
/// If the group does not exist, it will be added.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the <paramref name="item"/> should be added.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value. It will either be an existing group or a new group.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null)
|
||||
{
|
||||
group = new ObservableGroup<TKey, TValue>(key);
|
||||
source.Add(group);
|
||||
}
|
||||
|
||||
group.Add(item);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert <paramref name="item"/> into the first group with <paramref name="key"/> key at <paramref name="index"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static ObservableGroup<TKey, TValue> InsertItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? existingGroup = source.First(key);
|
||||
existingGroup.Insert(index, item);
|
||||
|
||||
return existingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the element at <paramref name="index"/> with <paramref name="item"/> in the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where to replace the item.</param>
|
||||
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static ObservableGroup<TKey, TValue> SetItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? existingGroup = source.First(key);
|
||||
existingGroup[index] = item;
|
||||
|
||||
return existingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the first occurrence of the group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to remove.</param>
|
||||
public static void RemoveGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static ObservableGroup<TKey, TValue>? FirstOrDefaultWithLinq(ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
{
|
||||
return source.FirstOrDefault(group => EqualityComparer<TKey>.Default.Equals(group.Key, key));
|
||||
}
|
||||
|
||||
return FirstOrDefaultWithLinq(source, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <param name="index">The index of the item from the targeted group.</param>
|
||||
/// <returns>The element.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
[Pure]
|
||||
public static TValue ElementAt<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index)
|
||||
where TKey : notnull
|
||||
=> source.First(key)[index];
|
||||
|
||||
/// <summary>
|
||||
/// Return the element at position <paramref name="index"/> from the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to query.</param>
|
||||
/// <param name="index">The index of the item from the targeted group.</param>
|
||||
/// <returns>The element or default(TValue) if it does not exist.</returns>
|
||||
[Pure]
|
||||
public static TValue? ElementAtOrDefault<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null ||
|
||||
(uint)index >= (uint)group.Count)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return group[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-value <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="value"/> will be added.</param>
|
||||
/// <param name="value">The value to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue value)
|
||||
where TKey : notnull
|
||||
=> AddGroup(source, key, new[] { value });
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
|
||||
/// <param name="collection">The collection to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
params TValue[] collection)
|
||||
where TKey : notnull
|
||||
=> source.AddGroup(key, (IEnumerable<TValue>)collection);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key-collection <see cref="ObservableGroup{TKey, TValue}"/> item into a target <see cref="ObservableGroupedCollection{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where <paramref name="collection"/> will be added.</param>
|
||||
/// <param name="collection">The collection to add.</param>
|
||||
/// <returns>The added <see cref="ObservableGroup{TKey, TValue}"/>.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
IEnumerable<TValue> collection)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = new(key, collection);
|
||||
source.Add(group);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add <paramref name="item"/> into the first group with <paramref name="key"/> key.
|
||||
/// If the group does not exist, it will be added.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the <paramref name="item"/> should be added.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value. It will either be an existing group or a new group.</returns>
|
||||
public static ObservableGroup<TKey, TValue> AddItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? group = source.FirstOrDefault(key);
|
||||
|
||||
if (group is null)
|
||||
{
|
||||
group = new ObservableGroup<TKey, TValue>(key);
|
||||
source.Add(group);
|
||||
}
|
||||
|
||||
group.Add(item);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert <paramref name="item"/> into the first group with <paramref name="key"/> key at <paramref name="index"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static ObservableGroup<TKey, TValue> InsertItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? existingGroup = source.First(key);
|
||||
existingGroup.Insert(index, item);
|
||||
|
||||
return existingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the element at <paramref name="index"/> with <paramref name="item"/> in the first group with <paramref name="key"/> key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where to replace the item.</param>
|
||||
/// <param name="index">The index where to insert <paramref name="item"/>.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <returns>The instance of the <see cref="ObservableGroup{TKey, TValue}"/> which will receive the value.</returns>
|
||||
/// <exception cref="InvalidOperationException">The target group does not exist.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static ObservableGroup<TKey, TValue> SetItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
TValue item)
|
||||
where TKey : notnull
|
||||
{
|
||||
ObservableGroup<TKey, TValue>? existingGroup = source.First(key);
|
||||
existingGroup[index] = item;
|
||||
|
||||
return existingGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the first occurrence of the group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group to remove.</param>
|
||||
public static void RemoveGroup<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
static void RemoveGroupWithLinq(ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void RemoveGroupWithLinq(ObservableGroupedCollection<TKey, TValue> source, TKey key)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
RemoveGroupWithLinq(source, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the first <paramref name="item"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group or the item does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the <paramref name="item"/> should be removed.</param>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
|
||||
public static void RemoveItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item,
|
||||
bool removeGroupIfEmpty = true)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
if (group.Remove(item) &&
|
||||
removeGroupIfEmpty &&
|
||||
group.Count == 0)
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveGroupWithLinq(source, key);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the first <paramref name="item"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group or the item does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the <paramref name="item"/> should be removed.</param>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
|
||||
public static void RemoveItem<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item,
|
||||
bool removeGroupIfEmpty = true)
|
||||
where TKey : notnull
|
||||
else
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void RemoveItemWithLinq(
|
||||
ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item,
|
||||
bool removeGroupIfEmpty)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
|
@ -344,61 +372,61 @@ namespace CommunityToolkit.Common.Collections
|
|||
index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
RemoveItemWithLinq(source, key, item, removeGroupIfEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the item at <paramref name="index"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group or the item does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the item at <paramref name="index"/> should be removed.</param>
|
||||
/// <param name="index">The index of the item to remove in the group.</param>
|
||||
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static void RemoveItemAt<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
bool removeGroupIfEmpty = true)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
{
|
||||
int groupIndex = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
{
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void RemoveItemWithLinq(
|
||||
ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
TValue item,
|
||||
bool removeGroupIfEmpty)
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
int index = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
group.RemoveAt(index);
|
||||
|
||||
if (removeGroupIfEmpty && group.Count == 0)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
if (group.Remove(item) &&
|
||||
removeGroupIfEmpty &&
|
||||
group.Count == 0)
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
source.RemoveAt(groupIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveItemWithLinq(source, key, item, removeGroupIfEmpty);
|
||||
groupIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the item at <paramref name="index"/> from the first group with <paramref name="key"/> from the <paramref name="source"/> grouped collection.
|
||||
/// It will not do anything if the group or the item does not exist.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
/// <param name="source">The source <see cref="ObservableGroupedCollection{TKey, TValue}"/> instance.</param>
|
||||
/// <param name="key">The key of the group where the item at <paramref name="index"/> should be removed.</param>
|
||||
/// <param name="index">The index of the item to remove in the group.</param>
|
||||
/// <param name="removeGroupIfEmpty">If true (default value), the group will be removed once it becomes empty.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero or <paramref name="index"/> is greater than the group elements' count.</exception>
|
||||
public static void RemoveItemAt<TKey, TValue>(
|
||||
this ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
bool removeGroupIfEmpty = true)
|
||||
where TKey : notnull
|
||||
else
|
||||
{
|
||||
if (source.TryGetList(out List<ObservableGroup<TKey, TValue>>? list))
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void RemoveItemAtWithLinq(
|
||||
ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
bool removeGroupIfEmpty)
|
||||
{
|
||||
int groupIndex = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in list)
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
|
@ -415,37 +443,8 @@ namespace CommunityToolkit.Common.Collections
|
|||
groupIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback method
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void RemoveItemAtWithLinq(
|
||||
ObservableGroupedCollection<TKey, TValue> source,
|
||||
TKey key,
|
||||
int index,
|
||||
bool removeGroupIfEmpty)
|
||||
{
|
||||
int groupIndex = 0;
|
||||
foreach (ObservableGroup<TKey, TValue>? group in source)
|
||||
{
|
||||
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
|
||||
{
|
||||
group.RemoveAt(index);
|
||||
|
||||
if (removeGroupIfEmpty && group.Count == 0)
|
||||
{
|
||||
source.RemoveAt(groupIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
groupIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveItemAtWithLinq(source, key, index, removeGroupIfEmpty);
|
||||
}
|
||||
RemoveItemAtWithLinq(source, key, index, removeGroupIfEmpty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,52 +6,51 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A read-only observable group. It associates a <see cref="Key"/> to a <see cref="ReadOnlyObservableCollection{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
public sealed class ReadOnlyObservableGroup<TKey, TValue> : ReadOnlyObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only observable group. It associates a <see cref="Key"/> to a <see cref="ReadOnlyObservableCollection{T}"/>.
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
|
||||
public sealed class ReadOnlyObservableGroup<TKey, TValue> : ReadOnlyObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
|
||||
where TKey : notnull
|
||||
/// <param name="key">The key of the group.</param>
|
||||
/// <param name="collection">The collection of items to add in the group.</param>
|
||||
public ReadOnlyObservableGroup(TKey key, ObservableCollection<TValue> collection)
|
||||
: base(collection)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the group.</param>
|
||||
/// <param name="collection">The collection of items to add in the group.</param>
|
||||
public ReadOnlyObservableGroup(TKey key, ObservableCollection<TValue> collection)
|
||||
: base(collection)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="group">The <see cref="ObservableGroup{TKey, TValue}"/> to wrap.</param>
|
||||
public ReadOnlyObservableGroup(ObservableGroup<TKey, TValue> group)
|
||||
: base(group)
|
||||
{
|
||||
Key = group.Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the group.</param>
|
||||
/// <param name="collection">The collection of items to add in the group.</param>
|
||||
public ReadOnlyObservableGroup(TKey key, IEnumerable<TValue> collection)
|
||||
: base(new ObservableCollection<TValue>(collection))
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TKey Key { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IReadOnlyObservableGroup.Key => Key;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="group">The <see cref="ObservableGroup{TKey, TValue}"/> to wrap.</param>
|
||||
public ReadOnlyObservableGroup(ObservableGroup<TKey, TValue> group)
|
||||
: base(group)
|
||||
{
|
||||
Key = group.Key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the group.</param>
|
||||
/// <param name="collection">The collection of items to add in the group.</param>
|
||||
public ReadOnlyObservableGroup(TKey key, IEnumerable<TValue> collection)
|
||||
: base(new ObservableCollection<TValue>(collection))
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TKey Key { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IReadOnlyObservableGroup.Key => Key;
|
||||
}
|
||||
|
|
|
@ -9,96 +9,95 @@ using System.Collections.Specialized;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// A read-only list of groups.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name = "TValue" > The type of the items in the collection.</typeparam>
|
||||
public sealed class ReadOnlyObservableGroupedCollection<TKey, TValue> : ReadOnlyObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only list of groups.
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the group key.</typeparam>
|
||||
/// <typeparam name = "TValue" > The type of the items in the collection.</typeparam>
|
||||
public sealed class ReadOnlyObservableGroupedCollection<TKey, TValue> : ReadOnlyObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
/// <param name="collection">The source collection to wrap.</param>
|
||||
public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection<TKey, TValue> collection)
|
||||
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g)))
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The source collection to wrap.</param>
|
||||
public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection<TKey, TValue> collection)
|
||||
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g)))
|
||||
{
|
||||
((INotifyCollectionChanged)collection).CollectionChanged += OnSourceCollectionChanged;
|
||||
}
|
||||
((INotifyCollectionChanged)collection).CollectionChanged += OnSourceCollectionChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<ReadOnlyObservableGroup<TKey, TValue>> collection)
|
||||
: base(new ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>(collection))
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<ReadOnlyObservableGroup<TKey, TValue>> collection)
|
||||
: base(new ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>(collection))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
|
||||
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g.Key, g)))
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The initial data to add in the grouped collection.</param>
|
||||
public ReadOnlyObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
|
||||
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g.Key, g)))
|
||||
{
|
||||
}
|
||||
|
||||
private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Even if NotifyCollectionChangedEventArgs allows multiple items, the actual implementation
|
||||
// is only reporting the changes one by one. We consider only this case for now.
|
||||
if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1)
|
||||
{
|
||||
// Even if NotifyCollectionChangedEventArgs allows multiple items, the actual implementation
|
||||
// is only reporting the changes one by one. We consider only this case for now.
|
||||
if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1)
|
||||
static void ThrowNotSupportedException()
|
||||
{
|
||||
static void ThrowNotSupportedException()
|
||||
throw new NotSupportedException(
|
||||
"ReadOnlyObservableGroupedCollection<TKey, TValue> doesn't support operations on multiple items at once.\n" +
|
||||
"If this exception was thrown, it likely means support for batched item updates has been added to the " +
|
||||
"underlying ObservableCollection<T> type, and this implementation doesn't support that feature yet.\n" +
|
||||
"Please consider opening an issue in https://aka.ms/windowstoolkit to report this.");
|
||||
}
|
||||
|
||||
ThrowNotSupportedException();
|
||||
}
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace:
|
||||
|
||||
// We only need to find the new item if the operation is either add or remove. In this
|
||||
// case we just directly find the first item that was modified, or throw if it's not present.
|
||||
// This normally never happens anyway - add and replace should always have a target element.
|
||||
ObservableGroup<TKey, TValue> newItem = e.NewItems!.Cast<ObservableGroup<TKey, TValue>>().First();
|
||||
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"ReadOnlyObservableGroupedCollection<TKey, TValue> doesn't support operations on multiple items at once.\n" +
|
||||
"If this exception was thrown, it likely means support for batched item updates has been added to the " +
|
||||
"underlying ObservableCollection<T> type, and this implementation doesn't support that feature yet.\n" +
|
||||
"Please consider opening an issue in https://aka.ms/windowstoolkit to report this.");
|
||||
Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
Items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(newItem);
|
||||
}
|
||||
|
||||
ThrowNotSupportedException();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace:
|
||||
|
||||
// We only need to find the new item if the operation is either add or remove. In this
|
||||
// case we just directly find the first item that was modified, or throw if it's not present.
|
||||
// This normally never happens anyway - add and replace should always have a target element.
|
||||
ObservableGroup<TKey, TValue> newItem = e.NewItems!.Cast<ObservableGroup<TKey, TValue>>().First();
|
||||
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
Items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(newItem);
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
|
||||
// Our inner Items list is our own ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> so we can safely cast Items to its concrete type here.
|
||||
((ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items).Move(e.OldStartingIndex, e.NewStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Items.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
Items.Clear();
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("unsupported value");
|
||||
break;
|
||||
}
|
||||
// Our inner Items list is our own ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> so we can safely cast Items to its concrete type here.
|
||||
((ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items).Move(e.OldStartingIndex, e.NewStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Items.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
Items.Clear();
|
||||
break;
|
||||
default:
|
||||
Debug.Fail("unsupported value");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,48 +2,47 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.Common
|
||||
namespace CommunityToolkit.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Set of helpers to convert between data types and notations.
|
||||
/// </summary>
|
||||
public static class Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of helpers to convert between data types and notations.
|
||||
/// Translate numeric file size in bytes to a human-readable shorter string format.
|
||||
/// </summary>
|
||||
public static class Converters
|
||||
/// <param name="size">File size in bytes.</param>
|
||||
/// <returns>Returns file size short string.</returns>
|
||||
public static string ToFileSizeString(long size)
|
||||
{
|
||||
/// <summary>
|
||||
/// Translate numeric file size in bytes to a human-readable shorter string format.
|
||||
/// </summary>
|
||||
/// <param name="size">File size in bytes.</param>
|
||||
/// <returns>Returns file size short string.</returns>
|
||||
public static string ToFileSizeString(long size)
|
||||
if (size < 1024)
|
||||
{
|
||||
if (size < 1024)
|
||||
{
|
||||
return size.ToString("F0") + " bytes";
|
||||
}
|
||||
else if ((size >> 10) < 1024)
|
||||
{
|
||||
return (size / 1024F).ToString("F1") + " KB";
|
||||
}
|
||||
else if ((size >> 20) < 1024)
|
||||
{
|
||||
return ((size >> 10) / 1024F).ToString("F1") + " MB";
|
||||
}
|
||||
else if ((size >> 30) < 1024)
|
||||
{
|
||||
return ((size >> 20) / 1024F).ToString("F1") + " GB";
|
||||
}
|
||||
else if ((size >> 40) < 1024)
|
||||
{
|
||||
return ((size >> 30) / 1024F).ToString("F1") + " TB";
|
||||
}
|
||||
else if ((size >> 50) < 1024)
|
||||
{
|
||||
return ((size >> 40) / 1024F).ToString("F1") + " PB";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((size >> 50) / 1024F).ToString("F0") + " EB";
|
||||
}
|
||||
return size.ToString("F0") + " bytes";
|
||||
}
|
||||
else if ((size >> 10) < 1024)
|
||||
{
|
||||
return (size / 1024F).ToString("F1") + " KB";
|
||||
}
|
||||
else if ((size >> 20) < 1024)
|
||||
{
|
||||
return ((size >> 10) / 1024F).ToString("F1") + " MB";
|
||||
}
|
||||
else if ((size >> 30) < 1024)
|
||||
{
|
||||
return ((size >> 20) / 1024F).ToString("F1") + " GB";
|
||||
}
|
||||
else if ((size >> 40) < 1024)
|
||||
{
|
||||
return ((size >> 30) / 1024F).ToString("F1") + " TB";
|
||||
}
|
||||
else if ((size >> 50) < 1024)
|
||||
{
|
||||
return ((size >> 40) / 1024F).ToString("F1") + " PB";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((size >> 50) / 1024F).ToString("F0") + " EB";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.Common.Deferred
|
||||
namespace CommunityToolkit.Common.Deferred;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DeferredEventArgs"/> which can also be canceled.
|
||||
/// </summary>
|
||||
public class DeferredCancelEventArgs : DeferredEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DeferredEventArgs"/> which can also be canceled.
|
||||
/// Gets or sets a value indicating whether the event should be canceled.
|
||||
/// </summary>
|
||||
public class DeferredCancelEventArgs : DeferredEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the event should be canceled.
|
||||
/// </summary>
|
||||
public bool Cancel { get; set; }
|
||||
}
|
||||
}
|
||||
public bool Cancel { get; set; }
|
||||
}
|
||||
|
|
|
@ -7,54 +7,53 @@ using System.ComponentModel;
|
|||
|
||||
#pragma warning disable CA1001
|
||||
|
||||
namespace CommunityToolkit.Common.Deferred
|
||||
namespace CommunityToolkit.Common.Deferred;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EventArgs"/> which can retrieve a <see cref="EventDeferral"/> in order to process data asynchronously before an <see cref="EventHandler"/> completes and returns to the calling control.
|
||||
/// </summary>
|
||||
public class DeferredEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="EventArgs"/> which can retrieve a <see cref="EventDeferral"/> in order to process data asynchronously before an <see cref="EventHandler"/> completes and returns to the calling control.
|
||||
/// Gets a new <see cref="DeferredEventArgs"/> to use in cases where no <see cref="EventArgs"/> wish to be provided.
|
||||
/// </summary>
|
||||
public class DeferredEventArgs : EventArgs
|
||||
public static new DeferredEventArgs Empty => new();
|
||||
|
||||
private readonly object _eventDeferralLock = new();
|
||||
|
||||
private EventDeferral? _eventDeferral;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="EventDeferral"/> which can be completed when deferred event is ready to continue.
|
||||
/// </summary>
|
||||
/// <returns><see cref="EventDeferral"/> instance.</returns>
|
||||
public EventDeferral GetDeferral()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a new <see cref="DeferredEventArgs"/> to use in cases where no <see cref="EventArgs"/> wish to be provided.
|
||||
/// </summary>
|
||||
public static new DeferredEventArgs Empty => new();
|
||||
|
||||
private readonly object _eventDeferralLock = new();
|
||||
|
||||
private EventDeferral? _eventDeferral;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an <see cref="EventDeferral"/> which can be completed when deferred event is ready to continue.
|
||||
/// </summary>
|
||||
/// <returns><see cref="EventDeferral"/> instance.</returns>
|
||||
public EventDeferral GetDeferral()
|
||||
lock (_eventDeferralLock)
|
||||
{
|
||||
lock (_eventDeferralLock)
|
||||
{
|
||||
return _eventDeferral ??= new EventDeferral();
|
||||
}
|
||||
return _eventDeferral ??= new EventDeferral();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DO NOT USE - This is a support method used by <see cref="EventHandlerExtensions"/>. It is public only for
|
||||
/// additional usage within extensions for the UWP based TypedEventHandler extensions.
|
||||
/// </summary>
|
||||
/// <returns>Internal EventDeferral reference</returns>
|
||||
/// <summary>
|
||||
/// DO NOT USE - This is a support method used by <see cref="EventHandlerExtensions"/>. It is public only for
|
||||
/// additional usage within extensions for the UWP based TypedEventHandler extensions.
|
||||
/// </summary>
|
||||
/// <returns>Internal EventDeferral reference</returns>
|
||||
#if !NETSTANDARD1_4
|
||||
[Browsable(false)]
|
||||
#endif
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead.")]
|
||||
public EventDeferral? GetCurrentDeferralAndReset()
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead.")]
|
||||
public EventDeferral? GetCurrentDeferralAndReset()
|
||||
{
|
||||
lock (_eventDeferralLock)
|
||||
{
|
||||
lock (_eventDeferralLock)
|
||||
{
|
||||
EventDeferral? eventDeferral = _eventDeferral;
|
||||
EventDeferral? eventDeferral = _eventDeferral;
|
||||
|
||||
_eventDeferral = null;
|
||||
_eventDeferral = null;
|
||||
|
||||
return eventDeferral;
|
||||
}
|
||||
return eventDeferral;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,47 +9,46 @@ using System.Threading.Tasks;
|
|||
|
||||
#pragma warning disable CA1063
|
||||
|
||||
namespace CommunityToolkit.Common.Deferred
|
||||
namespace CommunityToolkit.Common.Deferred;
|
||||
|
||||
/// <summary>
|
||||
/// Deferral handle provided by a <see cref="DeferredEventArgs"/>.
|
||||
/// </summary>
|
||||
public class EventDeferral : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Deferral handle provided by a <see cref="DeferredEventArgs"/>.
|
||||
/// </summary>
|
||||
public class EventDeferral : IDisposable
|
||||
//// TODO: If/when .NET 5 is base, we can upgrade to non-generic version
|
||||
private readonly TaskCompletionSource<object?> _taskCompletionSource = new();
|
||||
|
||||
internal EventDeferral()
|
||||
{
|
||||
//// TODO: If/when .NET 5 is base, we can upgrade to non-generic version
|
||||
private readonly TaskCompletionSource<object?> _taskCompletionSource = new();
|
||||
}
|
||||
|
||||
internal EventDeferral()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Call when finished with the Deferral.
|
||||
/// </summary>
|
||||
public void Complete() => _taskCompletionSource.TrySetResult(null);
|
||||
|
||||
/// <summary>
|
||||
/// Call when finished with the Deferral.
|
||||
/// </summary>
|
||||
public void Complete() => _taskCompletionSource.TrySetResult(null);
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the <see cref="EventDeferral"/> to be completed by the event handler.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/>.</param>
|
||||
/// <returns><see cref="Task"/>.</returns>
|
||||
/// <summary>
|
||||
/// Waits for the <see cref="EventDeferral"/> to be completed by the event handler.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/>.</param>
|
||||
/// <returns><see cref="Task"/>.</returns>
|
||||
#if !NETSTANDARD1_4
|
||||
[Browsable(false)]
|
||||
#endif
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead on the DeferredEventArgs.")]
|
||||
public async Task WaitForCompletion(CancellationToken cancellationToken)
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is an internal only method to be used by EventHandler extension classes, public callers should call GetDeferral() instead on the DeferredEventArgs.")]
|
||||
public async Task WaitForCompletion(CancellationToken cancellationToken)
|
||||
{
|
||||
using (cancellationToken.Register(() => _taskCompletionSource.TrySetCanceled()))
|
||||
{
|
||||
using (cancellationToken.Register(() => _taskCompletionSource.TrySetCanceled()))
|
||||
{
|
||||
_ = await _taskCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Complete();
|
||||
_ = await _taskCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,162 +7,161 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CommunityToolkit.Common
|
||||
namespace CommunityToolkit.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with arrays.
|
||||
/// </summary>
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with arrays.
|
||||
/// Yields a column from a jagged array.
|
||||
/// An exception will be thrown if the column is out of bounds, and return default in places where there are no elements from inner arrays.
|
||||
/// Note: There is no equivalent GetRow method, as you can use array[row] to retrieve.
|
||||
/// </summary>
|
||||
public static class ArrayExtensions
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="rectarray">The source array.</param>
|
||||
/// <param name="column">Column record to retrieve, 0-based index.</param>
|
||||
/// <returns>Yielded enumerable of column elements for given column, and default values for smaller inner arrays.</returns>
|
||||
public static IEnumerable<T?> GetColumn<T>(this T?[][] rectarray, int column)
|
||||
{
|
||||
/// <summary>
|
||||
/// Yields a column from a jagged array.
|
||||
/// An exception will be thrown if the column is out of bounds, and return default in places where there are no elements from inner arrays.
|
||||
/// Note: There is no equivalent GetRow method, as you can use array[row] to retrieve.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="rectarray">The source array.</param>
|
||||
/// <param name="column">Column record to retrieve, 0-based index.</param>
|
||||
/// <returns>Yielded enumerable of column elements for given column, and default values for smaller inner arrays.</returns>
|
||||
public static IEnumerable<T?> GetColumn<T>(this T?[][] rectarray, int column)
|
||||
if (column < 0 || column >= rectarray.Max(array => array.Length))
|
||||
{
|
||||
if (column < 0 || column >= rectarray.Max(array => array.Length))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column));
|
||||
}
|
||||
|
||||
for (int r = 0; r < rectarray.GetLength(0); r++)
|
||||
{
|
||||
if (column >= rectarray[r].Length)
|
||||
{
|
||||
yield return default;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return rectarray[r][column];
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of an array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[] array)
|
||||
for (int r = 0; r < rectarray.GetLength(0); r++)
|
||||
{
|
||||
// The returned string will be in the following format:
|
||||
// [1, 2, 3]
|
||||
StringBuilder builder = new();
|
||||
if (column >= rectarray[r].Length)
|
||||
{
|
||||
yield return default;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return rectarray[r][column];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of an array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[] array)
|
||||
{
|
||||
// The returned string will be in the following format:
|
||||
// [1, 2, 3]
|
||||
StringBuilder builder = new();
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
_ = builder.Append(",\t");
|
||||
}
|
||||
|
||||
_ = builder.Append(array[i]?.ToString());
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a jagged array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="mdarray">The source array.</param>
|
||||
/// <returns>String representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[][] mdarray)
|
||||
{
|
||||
// The returned string uses the same format as the overload for 2D arrays
|
||||
StringBuilder builder = new();
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int i = 0; i < mdarray.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
_ = builder.Append(',');
|
||||
_ = builder.Append(Environment.NewLine);
|
||||
_ = builder.Append(' ');
|
||||
}
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
T?[] row = mdarray[i];
|
||||
|
||||
for (int j = 0; j < row.Length; j++)
|
||||
{
|
||||
if (i != 0)
|
||||
if (j != 0)
|
||||
{
|
||||
_ = builder.Append(",\t");
|
||||
}
|
||||
|
||||
_ = builder.Append(array[i]?.ToString());
|
||||
_ = builder.Append(row[j]?.ToString());
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a jagged array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="mdarray">The source array.</param>
|
||||
/// <returns>String representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[][] mdarray)
|
||||
{
|
||||
// The returned string uses the same format as the overload for 2D arrays
|
||||
StringBuilder builder = new();
|
||||
_ = builder.Append(']');
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int i = 0; i < mdarray.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
_ = builder.Append(',');
|
||||
_ = builder.Append(Environment.NewLine);
|
||||
_ = builder.Append(' ');
|
||||
}
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
T?[] row = mdarray[i];
|
||||
|
||||
for (int j = 0; j < row.Length; j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
_ = builder.Append(",\t");
|
||||
}
|
||||
|
||||
_ = builder.Append(row[j]?.ToString());
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a 2D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[,] array)
|
||||
{
|
||||
// The returned string will be in the following format:
|
||||
// [[1, 2, 3],
|
||||
// [4, 5, 6],
|
||||
// [7, 8, 9]]
|
||||
StringBuilder builder = new();
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
int
|
||||
height = array.GetLength(0),
|
||||
width = array.GetLength(1);
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
_ = builder.Append(',');
|
||||
_ = builder.Append(Environment.NewLine);
|
||||
_ = builder.Append(' ');
|
||||
}
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
_ = builder.Append(",\t");
|
||||
}
|
||||
|
||||
_ = builder.Append(array[i, j]?.ToString());
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string representation of a 2D array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the array.</typeparam>
|
||||
/// <param name="array">The source array.</param>
|
||||
/// <returns>The <see cref="string"/> representation of the array.</returns>
|
||||
public static string ToArrayString<T>(this T?[,] array)
|
||||
{
|
||||
// The returned string will be in the following format:
|
||||
// [[1, 2, 3],
|
||||
// [4, 5, 6],
|
||||
// [7, 8, 9]]
|
||||
StringBuilder builder = new();
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
int
|
||||
height = array.GetLength(0),
|
||||
width = array.GetLength(1);
|
||||
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
_ = builder.Append(',');
|
||||
_ = builder.Append(Environment.NewLine);
|
||||
_ = builder.Append(' ');
|
||||
}
|
||||
|
||||
_ = builder.Append('[');
|
||||
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
_ = builder.Append(",\t");
|
||||
}
|
||||
|
||||
_ = builder.Append(array[i, j]?.ToString());
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
}
|
||||
|
||||
_ = builder.Append(']');
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,61 +7,60 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Common.Deferred
|
||||
namespace CommunityToolkit.Common.Deferred;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions to <see cref="EventHandler{TEventArgs}"/> for Deferred Events.
|
||||
/// </summary>
|
||||
public static class EventHandlerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions to <see cref="EventHandler{TEventArgs}"/> for Deferred Events.
|
||||
/// Use to invoke an async <see cref="EventHandler{TEventArgs}"/> using <see cref="DeferredEventArgs"/>.
|
||||
/// </summary>
|
||||
public static class EventHandlerExtensions
|
||||
/// <typeparam name="T"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="EventHandler{TEventArgs}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
public static Task InvokeAsync<T>(this EventHandler<T>? eventHandler, object sender, T eventArgs)
|
||||
where T : DeferredEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Use to invoke an async <see cref="EventHandler{TEventArgs}"/> using <see cref="DeferredEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="EventHandler{TEventArgs}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
public static Task InvokeAsync<T>(this EventHandler<T>? eventHandler, object sender, T eventArgs)
|
||||
where T : DeferredEventArgs
|
||||
return InvokeAsync(eventHandler, sender, eventArgs, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to invoke an async <see cref="EventHandler{TEventArgs}"/> using <see cref="DeferredEventArgs"/> with a <see cref="CancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="EventHandler{TEventArgs}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/> option.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
public static Task InvokeAsync<T>(this EventHandler<T>? eventHandler, object sender, T eventArgs, CancellationToken cancellationToken)
|
||||
where T : DeferredEventArgs
|
||||
{
|
||||
if (eventHandler == null)
|
||||
{
|
||||
return InvokeAsync(eventHandler, sender, eventArgs, CancellationToken.None);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to invoke an async <see cref="EventHandler{TEventArgs}"/> using <see cref="DeferredEventArgs"/> with a <see cref="CancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="EventHandler{TEventArgs}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/> option.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
public static Task InvokeAsync<T>(this EventHandler<T>? eventHandler, object sender, T eventArgs, CancellationToken cancellationToken)
|
||||
where T : DeferredEventArgs
|
||||
{
|
||||
if (eventHandler == null)
|
||||
Task[]? tasks = eventHandler.GetInvocationList()
|
||||
.OfType<EventHandler<T>>()
|
||||
.Select(invocationDelegate =>
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Task[]? tasks = eventHandler.GetInvocationList()
|
||||
.OfType<EventHandler<T>>()
|
||||
.Select(invocationDelegate =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
invocationDelegate(sender, eventArgs);
|
||||
invocationDelegate(sender, eventArgs);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
EventDeferral? deferral = eventArgs.GetCurrentDeferralAndReset();
|
||||
|
||||
return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask;
|
||||
return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
})
|
||||
.ToArray();
|
||||
.ToArray();
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,78 +5,77 @@
|
|||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Helpers;
|
||||
|
||||
namespace CommunityToolkit.Common.Extensions
|
||||
namespace CommunityToolkit.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers methods for working with <see cref="ISettingsStorageHelper{TKey}"/> implementations.
|
||||
/// </summary>
|
||||
public static class ISettingsStorageHelperExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers methods for working with <see cref="ISettingsStorageHelper{TKey}"/> implementations.
|
||||
/// Attempts to read the provided key and return the value.
|
||||
/// If the key is not found, the fallback value will be used instead.
|
||||
/// </summary>
|
||||
public static class ISettingsStorageHelperExtensions
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <typeparam name="TValue">The type of object value expected.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance fo read from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <param name="fallback">An alternative value returned if the read fails.</param>
|
||||
/// <returns>The value of the target object, or the fallback value.</returns>
|
||||
public static TValue? GetValueOrDefault<TKey, TValue>(this ISettingsStorageHelper<TKey> storageHelper, TKey key, TValue? fallback = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to read the provided key and return the value.
|
||||
/// If the key is not found, the fallback value will be used instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <typeparam name="TValue">The type of object value expected.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance fo read from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <param name="fallback">An alternative value returned if the read fails.</param>
|
||||
/// <returns>The value of the target object, or the fallback value.</returns>
|
||||
public static TValue? GetValueOrDefault<TKey, TValue>(this ISettingsStorageHelper<TKey> storageHelper, TKey key, TValue? fallback = default)
|
||||
where TKey : notnull
|
||||
if (storageHelper.TryRead(key, out TValue? storedValue))
|
||||
{
|
||||
if (storageHelper.TryRead(key, out TValue? storedValue))
|
||||
{
|
||||
return storedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
return storedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the key in the storage helper instance and get the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <typeparam name="TValue">The type of object value expected.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance fo read from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <returns>The value of the target object</returns>
|
||||
/// <exception cref="KeyNotFoundException">Throws when the key is not found in storage.</exception>
|
||||
public static TValue? Read<TKey, TValue>(this ISettingsStorageHelper<TKey> storageHelper, TKey key)
|
||||
where TKey : notnull
|
||||
else
|
||||
{
|
||||
if (storageHelper.TryRead<TValue>(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowKeyNotFoundException(key);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a key from storage.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance to delete from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <exception cref="KeyNotFoundException">Throws when the key is not found in storage.</exception>
|
||||
public static void Delete<TKey>(this ISettingsStorageHelper<TKey> storageHelper, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (!storageHelper.TryDelete(key))
|
||||
{
|
||||
ThrowKeyNotFoundException(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowKeyNotFoundException<TKey>(TKey key)
|
||||
{
|
||||
throw new KeyNotFoundException($"The given key '{key}' was not present");
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the key in the storage helper instance and get the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <typeparam name="TValue">The type of object value expected.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance fo read from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <returns>The value of the target object</returns>
|
||||
/// <exception cref="KeyNotFoundException">Throws when the key is not found in storage.</exception>
|
||||
public static TValue? Read<TKey, TValue>(this ISettingsStorageHelper<TKey> storageHelper, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (storageHelper.TryRead<TValue>(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowKeyNotFoundException(key);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a key from storage.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of key used to lookup the object.</typeparam>
|
||||
/// <param name="storageHelper">The storage helper instance to delete from.</param>
|
||||
/// <param name="key">The key of the target object.</param>
|
||||
/// <exception cref="KeyNotFoundException">Throws when the key is not found in storage.</exception>
|
||||
public static void Delete<TKey>(this ISettingsStorageHelper<TKey> storageHelper, TKey key)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (!storageHelper.TryDelete(key))
|
||||
{
|
||||
ThrowKeyNotFoundException(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowKeyNotFoundException<TKey>(TKey key)
|
||||
{
|
||||
throw new KeyNotFoundException($"The given key '{key}' was not present");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,181 +8,180 @@ using System.Globalization;
|
|||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CommunityToolkit.Common
|
||||
namespace CommunityToolkit.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with strings and string representations.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with strings and string representations.
|
||||
/// Regular expression for matching a phone number.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
internal const string PhoneNumberRegex = @"^[+]?(\d{1,3})?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for matching a string that contains only letters.
|
||||
/// </summary>
|
||||
internal const string CharactersRegex = "^[A-Za-z]+$";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for matching an email address.
|
||||
/// </summary>
|
||||
/// <remarks>General Email Regex (RFC 5322 Official Standard) from https://emailregex.com.</remarks>
|
||||
internal const string EmailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression of HTML tags to remove.
|
||||
/// </summary>
|
||||
private const string RemoveHtmlTagsRegex = @"(?></?\w+)(?>(?:[^>'""]+|'[^']*'|""[^""]*"")*)>";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing comments from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlCommentsRegex = new("<!--.*?-->", RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing scripts from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlScriptsRegex = new(@"(?s)<script.*?(/>|</script>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing styles from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlStylesRegex = new(@"(?s)<style.*?(/>|</style>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid email address.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid email address; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsEmail(this string str) => Regex.IsMatch(str, EmailRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid decimal number.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid decimal number; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsDecimal([NotNullWhen(true)] this string? str)
|
||||
{
|
||||
/// <summary>
|
||||
/// Regular expression for matching a phone number.
|
||||
/// </summary>
|
||||
internal const string PhoneNumberRegex = @"^[+]?(\d{1,3})?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for matching a string that contains only letters.
|
||||
/// </summary>
|
||||
internal const string CharactersRegex = "^[A-Za-z]+$";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for matching an email address.
|
||||
/// </summary>
|
||||
/// <remarks>General Email Regex (RFC 5322 Official Standard) from https://emailregex.com.</remarks>
|
||||
internal const string EmailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression of HTML tags to remove.
|
||||
/// </summary>
|
||||
private const string RemoveHtmlTagsRegex = @"(?></?\w+)(?>(?:[^>'""]+|'[^']*'|""[^""]*"")*)>";
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing comments from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlCommentsRegex = new("<!--.*?-->", RegexOptions.Singleline);
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing scripts from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlScriptsRegex = new(@"(?s)<script.*?(/>|</script>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for removing styles from HTML.
|
||||
/// </summary>
|
||||
private static readonly Regex RemoveHtmlStylesRegex = new(@"(?s)<style.*?(/>|</style>)", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid email address.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid email address; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsEmail(this string str) => Regex.IsMatch(str, EmailRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid decimal number.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid decimal number; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsDecimal([NotNullWhen(true)] this string? str)
|
||||
{
|
||||
return decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid integer.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid integer; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsNumeric([NotNullWhen(true)] this string? str)
|
||||
{
|
||||
return int.TryParse(str, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid phone number.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid phone number; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsPhoneNumber(this string str) => Regex.IsMatch(str, PhoneNumberRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string contains only letters.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> if the string contains only letters; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with HTML comments, scripts, styles, and tags removed.
|
||||
/// </summary>
|
||||
/// <param name="htmlText">HTML string.</param>
|
||||
/// <returns>Decoded HTML string.</returns>
|
||||
[return: NotNullIfNotNull("htmlText")]
|
||||
public static string? DecodeHtml(this string? htmlText)
|
||||
{
|
||||
if (htmlText is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string? ret = htmlText.FixHtml();
|
||||
|
||||
// Remove html tags
|
||||
ret = new Regex(RemoveHtmlTagsRegex).Replace(ret, string.Empty);
|
||||
|
||||
return WebUtility.HtmlDecode(ret);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with HTML comments, scripts, and styles removed.
|
||||
/// </summary>
|
||||
/// <param name="html">HTML string to fix.</param>
|
||||
/// <returns>Fixed HTML string.</returns>
|
||||
public static string FixHtml(this string html)
|
||||
{
|
||||
// Remove comments
|
||||
string? withoutComments = RemoveHtmlCommentsRegex.Replace(html, string.Empty);
|
||||
|
||||
// Remove scripts
|
||||
string? withoutScripts = RemoveHtmlScriptsRegex.Replace(withoutComments, string.Empty);
|
||||
|
||||
// Remove styles
|
||||
string? withoutStyles = RemoveHtmlStylesRegex.Replace(withoutScripts, string.Empty);
|
||||
|
||||
return withoutStyles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to the specified length.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to be truncated.</param>
|
||||
/// <param name="length">The maximum length.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
public static string Truncate(this string? value, int length) => Truncate(value, length, false);
|
||||
|
||||
/// <summary>
|
||||
/// Provide better linking for resourced strings.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of the string being linked.</param>
|
||||
/// <param name="args">The object which will receive the linked String.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
[Obsolete("This method will be removed in a future version of the Toolkit. Use the native C# string interpolation syntax instead, see: https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated")]
|
||||
public static string AsFormat(this string format, params object[] args)
|
||||
{
|
||||
// Note: this extension was originally added to help developers using {x:Bind} in XAML, but
|
||||
// due to a known limitation in the UWP/WinUI XAML compiler, using either this method or the
|
||||
// standard string.Format method from the BCL directly doesn't always work. Since this method
|
||||
// doesn't actually provide any benefit over the built-in one, it has been marked as obsolete.
|
||||
// For more details, see the WinUI issue on the XAML compiler limitation here:
|
||||
// https://github.com/microsoft/microsoft-ui-xaml/issues/2654.
|
||||
return string.Format(format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to the specified length.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to be truncated.</param>
|
||||
/// <param name="length">The maximum length.</param>
|
||||
/// <param name="ellipsis"><c>true</c> to add ellipsis to the truncated text; otherwise, <c>false</c>.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
public static string Truncate(this string? value, int length, bool ellipsis)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = value!.Trim();
|
||||
|
||||
if (value.Length > length)
|
||||
{
|
||||
if (ellipsis)
|
||||
{
|
||||
return value.Substring(0, length) + "...";
|
||||
}
|
||||
|
||||
return value.Substring(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
return decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid integer.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid integer; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsNumeric([NotNullWhen(true)] this string? str)
|
||||
{
|
||||
return int.TryParse(str, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string is a valid phone number.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> for a valid phone number; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsPhoneNumber(this string str) => Regex.IsMatch(str, PhoneNumberRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a string contains only letters.
|
||||
/// </summary>
|
||||
/// <param name="str">The string to test.</param>
|
||||
/// <returns><c>true</c> if the string contains only letters; otherwise, <c>false</c>.</returns>
|
||||
public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with HTML comments, scripts, styles, and tags removed.
|
||||
/// </summary>
|
||||
/// <param name="htmlText">HTML string.</param>
|
||||
/// <returns>Decoded HTML string.</returns>
|
||||
[return: NotNullIfNotNull("htmlText")]
|
||||
public static string? DecodeHtml(this string? htmlText)
|
||||
{
|
||||
if (htmlText is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string? ret = htmlText.FixHtml();
|
||||
|
||||
// Remove html tags
|
||||
ret = new Regex(RemoveHtmlTagsRegex).Replace(ret, string.Empty);
|
||||
|
||||
return WebUtility.HtmlDecode(ret);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string with HTML comments, scripts, and styles removed.
|
||||
/// </summary>
|
||||
/// <param name="html">HTML string to fix.</param>
|
||||
/// <returns>Fixed HTML string.</returns>
|
||||
public static string FixHtml(this string html)
|
||||
{
|
||||
// Remove comments
|
||||
string? withoutComments = RemoveHtmlCommentsRegex.Replace(html, string.Empty);
|
||||
|
||||
// Remove scripts
|
||||
string? withoutScripts = RemoveHtmlScriptsRegex.Replace(withoutComments, string.Empty);
|
||||
|
||||
// Remove styles
|
||||
string? withoutStyles = RemoveHtmlStylesRegex.Replace(withoutScripts, string.Empty);
|
||||
|
||||
return withoutStyles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to the specified length.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to be truncated.</param>
|
||||
/// <param name="length">The maximum length.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
public static string Truncate(this string? value, int length) => Truncate(value, length, false);
|
||||
|
||||
/// <summary>
|
||||
/// Provide better linking for resourced strings.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of the string being linked.</param>
|
||||
/// <param name="args">The object which will receive the linked String.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
[Obsolete("This method will be removed in a future version of the Toolkit. Use the native C# string interpolation syntax instead, see: https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated")]
|
||||
public static string AsFormat(this string format, params object[] args)
|
||||
{
|
||||
// Note: this extension was originally added to help developers using {x:Bind} in XAML, but
|
||||
// due to a known limitation in the UWP/WinUI XAML compiler, using either this method or the
|
||||
// standard string.Format method from the BCL directly doesn't always work. Since this method
|
||||
// doesn't actually provide any benefit over the built-in one, it has been marked as obsolete.
|
||||
// For more details, see the WinUI issue on the XAML compiler limitation here:
|
||||
// https://github.com/microsoft/microsoft-ui-xaml/issues/2654.
|
||||
return string.Format(format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to the specified length.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to be truncated.</param>
|
||||
/// <param name="length">The maximum length.</param>
|
||||
/// <param name="ellipsis"><c>true</c> to add ellipsis to the truncated text; otherwise, <c>false</c>.</param>
|
||||
/// <returns>Truncated string.</returns>
|
||||
public static string Truncate(this string? value, int length, bool ellipsis)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = value!.Trim();
|
||||
|
||||
if (value.Length > length)
|
||||
{
|
||||
if (ellipsis)
|
||||
{
|
||||
return value.Substring(0, length) + "...";
|
||||
}
|
||||
|
||||
return value.Substring(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,79 +7,78 @@ using System.Reflection;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Common
|
||||
namespace CommunityToolkit.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with tasks.
|
||||
/// </summary>
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with tasks.
|
||||
/// Gets the result of a <see cref="Task"/> if available, or <see langword="null"/> otherwise.
|
||||
/// </summary>
|
||||
public static class TaskExtensions
|
||||
/// <param name="task">The input <see cref="Task"/> instance to get the result for.</param>
|
||||
/// <returns>The result of <paramref name="task"/> if completed successfully, or <see langword="default"/> otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// This method does not block if <paramref name="task"/> has not completed yet. Furthermore, it is not generic
|
||||
/// and uses reflection to access the <see cref="Task{TResult}.Result"/> property and boxes the result if it's
|
||||
/// a value type, which adds overhead. It should only be used when using generics is not possible.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static object? GetResultOrDefault(this Task task)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the result of a <see cref="Task"/> if available, or <see langword="null"/> otherwise.
|
||||
/// </summary>
|
||||
/// <param name="task">The input <see cref="Task"/> instance to get the result for.</param>
|
||||
/// <returns>The result of <paramref name="task"/> if completed successfully, or <see langword="default"/> otherwise.</returns>
|
||||
/// <remarks>
|
||||
/// This method does not block if <paramref name="task"/> has not completed yet. Furthermore, it is not generic
|
||||
/// and uses reflection to access the <see cref="Task{TResult}.Result"/> property and boxes the result if it's
|
||||
/// a value type, which adds overhead. It should only be used when using generics is not possible.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static object? GetResultOrDefault(this Task task)
|
||||
{
|
||||
// Check if the instance is a completed Task
|
||||
if (
|
||||
// Check if the instance is a completed Task
|
||||
if (
|
||||
#if NETSTANDARD2_1
|
||||
task.IsCompletedSuccessfully
|
||||
#else
|
||||
task.Status == TaskStatus.RanToCompletion
|
||||
#endif
|
||||
)
|
||||
{
|
||||
// We need an explicit check to ensure the input task is not the cached
|
||||
// Task.CompletedTask instance, because that can internally be stored as
|
||||
// a Task<T> for some given T (eg. on .NET 5 it's VoidTaskResult), which
|
||||
// would cause the following code to return that result instead of null.
|
||||
if (task != Task.CompletedTask)
|
||||
{
|
||||
// We need an explicit check to ensure the input task is not the cached
|
||||
// Task.CompletedTask instance, because that can internally be stored as
|
||||
// a Task<T> for some given T (eg. on .NET 5 it's VoidTaskResult), which
|
||||
// would cause the following code to return that result instead of null.
|
||||
if (task != Task.CompletedTask)
|
||||
{
|
||||
// Try to get the Task<T>.Result property. This method would've
|
||||
// been called anyway after the type checks, but using that to
|
||||
// validate the input type saves some additional reflection calls.
|
||||
// Furthermore, doing this also makes the method flexible enough to
|
||||
// cases whether the input Task<T> is actually an instance of some
|
||||
// runtime-specific type that inherits from Task<T>.
|
||||
PropertyInfo? propertyInfo =
|
||||
// Try to get the Task<T>.Result property. This method would've
|
||||
// been called anyway after the type checks, but using that to
|
||||
// validate the input type saves some additional reflection calls.
|
||||
// Furthermore, doing this also makes the method flexible enough to
|
||||
// cases whether the input Task<T> is actually an instance of some
|
||||
// runtime-specific type that inherits from Task<T>.
|
||||
PropertyInfo? propertyInfo =
|
||||
#if NETSTANDARD1_4
|
||||
task.GetType().GetRuntimeProperty(nameof(Task<object>.Result));
|
||||
#else
|
||||
task.GetType().GetProperty(nameof(Task<object>.Result));
|
||||
#endif
|
||||
|
||||
// Return the result, if possible
|
||||
return propertyInfo?.GetValue(task);
|
||||
}
|
||||
// Return the result, if possible
|
||||
return propertyInfo?.GetValue(task);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of a <see cref="Task{TResult}"/> if available, or <see langword="default"/> otherwise.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see cref="Task{TResult}"/> to get the result for.</typeparam>
|
||||
/// <param name="task">The input <see cref="Task{TResult}"/> instance to get the result for.</param>
|
||||
/// <returns>The result of <paramref name="task"/> if completed successfully, or <see langword="default"/> otherwise.</returns>
|
||||
/// <remarks>This method does not block if <paramref name="task"/> has not completed yet.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetResultOrDefault<T>(this Task<T?> task)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of a <see cref="Task{TResult}"/> if available, or <see langword="default"/> otherwise.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of <see cref="Task{TResult}"/> to get the result for.</typeparam>
|
||||
/// <param name="task">The input <see cref="Task{TResult}"/> instance to get the result for.</param>
|
||||
/// <returns>The result of <paramref name="task"/> if completed successfully, or <see langword="default"/> otherwise.</returns>
|
||||
/// <remarks>This method does not block if <paramref name="task"/> has not completed yet.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetResultOrDefault<T>(this Task<T?> task)
|
||||
{
|
||||
#if NETSTANDARD2_1
|
||||
return task.IsCompletedSuccessfully ? task.Result : default;
|
||||
#else
|
||||
return task.Status == TaskStatus.RanToCompletion ? task.Result : default;
|
||||
return task.Status == TaskStatus.RanToCompletion ? task.Result : default;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,26 +2,25 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.Common.Helpers
|
||||
namespace CommunityToolkit.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the types of items available in a directory.
|
||||
/// </summary>
|
||||
public enum DirectoryItemType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the types of items available in a directory.
|
||||
/// The item is neither a file or a folder.
|
||||
/// </summary>
|
||||
public enum DirectoryItemType
|
||||
{
|
||||
/// <summary>
|
||||
/// The item is neither a file or a folder.
|
||||
/// </summary>
|
||||
None,
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a file type item.
|
||||
/// </summary>
|
||||
File,
|
||||
/// <summary>
|
||||
/// Represents a file type item.
|
||||
/// </summary>
|
||||
File,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a folder type item.
|
||||
/// </summary>
|
||||
Folder
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a folder type item.
|
||||
/// </summary>
|
||||
Folder
|
||||
}
|
||||
|
|
|
@ -6,63 +6,62 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Common.Helpers
|
||||
namespace CommunityToolkit.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Service interface used to store data in a directory/file-system via files and folders.
|
||||
///
|
||||
/// This interface is meant to help abstract file storage operations across platforms in a library,
|
||||
/// but the actual behavior will be up to the implementer. Such as, we don't provide a sense of a current directory,
|
||||
/// so an implementor should consider using full paths to support any file operations. Otherwise, a "directory aware"
|
||||
/// implementation could be achieved with a current directory field and traversal functions, in which case relative paths would be applicable.
|
||||
/// </summary>
|
||||
public interface IFileStorageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Service interface used to store data in a directory/file-system via files and folders.
|
||||
///
|
||||
/// This interface is meant to help abstract file storage operations across platforms in a library,
|
||||
/// but the actual behavior will be up to the implementer. Such as, we don't provide a sense of a current directory,
|
||||
/// so an implementor should consider using full paths to support any file operations. Otherwise, a "directory aware"
|
||||
/// implementation could be achieved with a current directory field and traversal functions, in which case relative paths would be applicable.
|
||||
/// Retrieves an object from a file.
|
||||
/// </summary>
|
||||
public interface IFileStorageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves an object from a file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object retrieved.</typeparam>
|
||||
/// <param name="filePath">Path to the file that contains the object.</param>
|
||||
/// <param name="default">Default value of the object.</param>
|
||||
/// <returns>Waiting task until completion with the object in the file.</returns>
|
||||
Task<T?> ReadFileAsync<T>(string filePath, T? @default = default);
|
||||
/// <typeparam name="T">Type of object retrieved.</typeparam>
|
||||
/// <param name="filePath">Path to the file that contains the object.</param>
|
||||
/// <param name="default">Default value of the object.</param>
|
||||
/// <returns>Waiting task until completion with the object in the file.</returns>
|
||||
Task<T?> ReadFileAsync<T>(string filePath, T? @default = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the listings for a folder and the item types.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The path to the target folder.</param>
|
||||
/// <returns>A list of item types and names in the target folder.</returns>
|
||||
Task<IEnumerable<(DirectoryItemType ItemType, string Name)>> ReadFolderAsync(string folderPath);
|
||||
/// <summary>
|
||||
/// Retrieves the listings for a folder and the item types.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The path to the target folder.</param>
|
||||
/// <returns>A list of item types and names in the target folder.</returns>
|
||||
Task<IEnumerable<(DirectoryItemType ItemType, string Name)>> ReadFolderAsync(string folderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Saves an object inside a file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object saved.</typeparam>
|
||||
/// <param name="filePath">Path to the file that will contain the object.</param>
|
||||
/// <param name="value">Object to save.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task CreateFileAsync<T>(string filePath, T value);
|
||||
/// <summary>
|
||||
/// Saves an object inside a file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object saved.</typeparam>
|
||||
/// <param name="filePath">Path to the file that will contain the object.</param>
|
||||
/// <param name="value">Object to save.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task CreateFileAsync<T>(string filePath, T value);
|
||||
|
||||
/// <summary>
|
||||
/// Ensure a folder exists at the folder path specified.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The path and name of the target folder.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task CreateFolderAsync(string folderPath);
|
||||
/// <summary>
|
||||
/// Ensure a folder exists at the folder path specified.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The path and name of the target folder.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task CreateFolderAsync(string folderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file or folder item.
|
||||
/// </summary>
|
||||
/// <param name="itemPath">The path to the item for deletion.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task<bool> TryDeleteItemAsync(string itemPath);
|
||||
/// <summary>
|
||||
/// Deletes a file or folder item.
|
||||
/// </summary>
|
||||
/// <param name="itemPath">The path to the item for deletion.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task<bool> TryDeleteItemAsync(string itemPath);
|
||||
|
||||
/// <summary>
|
||||
/// Rename an item.
|
||||
/// </summary>
|
||||
/// <param name="itemPath">The path to the target item.</param>
|
||||
/// <param name="newName">The new nam for the target item.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task<bool> TryRenameItemAsync(string itemPath, string newName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rename an item.
|
||||
/// </summary>
|
||||
/// <param name="itemPath">The path to the target item.</param>
|
||||
/// <param name="newName">The new nam for the target item.</param>
|
||||
/// <returns>Waiting task until completion.</returns>
|
||||
Task<bool> TryRenameItemAsync(string itemPath, string newName);
|
||||
}
|
||||
|
|
|
@ -2,27 +2,26 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.Common.Helpers
|
||||
namespace CommunityToolkit.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// A basic serialization service.
|
||||
/// </summary>
|
||||
public interface IObjectSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// A basic serialization service.
|
||||
/// Serialize an object into a string. It is recommended to use strings as the final format for objects.
|
||||
/// </summary>
|
||||
public interface IObjectSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize an object into a string. It is recommended to use strings as the final format for objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to serialize.</typeparam>
|
||||
/// <param name="value">The object to serialize.</param>
|
||||
/// <returns>The serialized object.</returns>
|
||||
string? Serialize<T>(T value);
|
||||
/// <typeparam name="T">The type of the object to serialize.</typeparam>
|
||||
/// <param name="value">The object to serialize.</param>
|
||||
/// <returns>The serialized object.</returns>
|
||||
string? Serialize<T>(T value);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize string into an object of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the deserialized object.</typeparam>
|
||||
/// <param name="value">The string to deserialize.</param>
|
||||
/// <returns>The deserialized object.</returns>
|
||||
T Deserialize<T>(string value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Deserialize string into an object of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the deserialized object.</typeparam>
|
||||
/// <param name="value">The string to deserialize.</param>
|
||||
/// <returns>The deserialized object.</returns>
|
||||
T Deserialize<T>(string value);
|
||||
}
|
||||
|
|
|
@ -2,42 +2,41 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.Helpers
|
||||
namespace CommunityToolkit.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Service interface used to store data using key value pairs.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of keys to use for accessing values.</typeparam>
|
||||
public interface ISettingsStorageHelper<in TKey>
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Service interface used to store data using key value pairs.
|
||||
/// Retrieves a single item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of keys to use for accessing values.</typeparam>
|
||||
public interface ISettingsStorageHelper<in TKey>
|
||||
where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a single item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of object retrieved.</typeparam>
|
||||
/// <param name="key">Key of the object.</param>
|
||||
/// <param name="value">The <see typeparamref="TValue"/> object for <see typeparamref="TKey"/> key.</param>
|
||||
/// <returns>A boolean indicator of success.</returns>
|
||||
bool TryRead<TValue>(TKey key, out TValue? value);
|
||||
/// <typeparam name="TValue">Type of object retrieved.</typeparam>
|
||||
/// <param name="key">Key of the object.</param>
|
||||
/// <param name="value">The <see typeparamref="TValue"/> object for <see typeparamref="TKey"/> key.</param>
|
||||
/// <returns>A boolean indicator of success.</returns>
|
||||
bool TryRead<TValue>(TKey key, out TValue? value);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a single item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of object saved.</typeparam>
|
||||
/// <param name="key">Key of the value saved.</param>
|
||||
/// <param name="value">Object to save.</param>
|
||||
void Save<TValue>(TKey key, TValue value);
|
||||
/// <summary>
|
||||
/// Saves a single item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Type of object saved.</typeparam>
|
||||
/// <param name="key">Key of the value saved.</param>
|
||||
/// <param name="value">Object to save.</param>
|
||||
void Save<TValue>(TKey key, TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single item by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the object.</param>
|
||||
/// <returns>A boolean indicator of success.</returns>
|
||||
bool TryDelete(TKey key);
|
||||
/// <summary>
|
||||
/// Deletes a single item by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the object.</param>
|
||||
/// <returns>A boolean indicator of success.</returns>
|
||||
bool TryDelete(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all keys and values from the settings store.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Clear all keys and values from the settings store.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
|
|
|
@ -5,42 +5,41 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CommunityToolkit.Common.Helpers
|
||||
namespace CommunityToolkit.Common.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// A bare-bones serializer which knows how to deal with primitive types and strings only.
|
||||
/// It is recommended for more complex scenarios to implement your own <see cref="IObjectSerializer"/> based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration
|
||||
/// </summary>
|
||||
public class SystemSerializer : IObjectSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// A bare-bones serializer which knows how to deal with primitive types and strings only.
|
||||
/// It is recommended for more complex scenarios to implement your own <see cref="IObjectSerializer"/> based on System.Text.Json, Newtonsoft.Json, or DataContractJsonSerializer see https://aka.ms/wct/storagehelper-migration
|
||||
/// Take a primitive value from storage and return it as the requested type using the <see cref="Convert.ChangeType(object, Type)"/> API.
|
||||
/// </summary>
|
||||
public class SystemSerializer : IObjectSerializer
|
||||
/// <typeparam name="T">Type to convert value to.</typeparam>
|
||||
/// <param name="value">Value from storage to convert.</param>
|
||||
/// <returns>Deserialized value or default value.</returns>
|
||||
public T Deserialize<T>(string value)
|
||||
{
|
||||
/// <summary>
|
||||
/// Take a primitive value from storage and return it as the requested type using the <see cref="Convert.ChangeType(object, Type)"/> API.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert value to.</typeparam>
|
||||
/// <param name="value">Value from storage to convert.</param>
|
||||
/// <returns>Deserialized value or default value.</returns>
|
||||
public T Deserialize<T>(string value)
|
||||
Type? type = typeof(T);
|
||||
TypeInfo? typeInfo = type.GetTypeInfo();
|
||||
|
||||
if (typeInfo.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
Type? type = typeof(T);
|
||||
TypeInfo? typeInfo = type.GetTypeInfo();
|
||||
|
||||
if (typeInfo.IsPrimitive || type == typeof(string))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, type);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("This serializer can only handle primitive types and strings. Please implement your own IObjectSerializer for more complex scenarios.");
|
||||
return (T)Convert.ChangeType(value, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value so that it can be serialized directly.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to serialize from.</typeparam>
|
||||
/// <param name="value">Value to serialize.</param>
|
||||
/// <returns>String representation of value.</returns>
|
||||
public string? Serialize<T>(T value)
|
||||
{
|
||||
return value?.ToString();
|
||||
}
|
||||
throw new NotSupportedException("This serializer can only handle primitive types and strings. Please implement your own IObjectSerializer for more complex scenarios.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value so that it can be serialized directly.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to serialize from.</typeparam>
|
||||
/// <param name="value">Value to serialize.</param>
|
||||
/// <returns>String representation of value.</returns>
|
||||
public string? Serialize<T>(T value)
|
||||
{
|
||||
return value?.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,29 +6,28 @@ using System.Collections.Generic;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Common.Collections
|
||||
namespace CommunityToolkit.Common.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents a data source whose items can be loaded incrementally.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">Type of collection element.</typeparam>
|
||||
public interface IIncrementalSource<TSource>
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents a data source whose items can be loaded incrementally.
|
||||
/// This method is invoked every time the view need to show more items. Retrieves items based on <paramref name="pageIndex"/> and <paramref name="pageSize"/> arguments.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">Type of collection element.</typeparam>
|
||||
public interface IIncrementalSource<TSource>
|
||||
{
|
||||
/// <summary>
|
||||
/// This method is invoked every time the view need to show more items. Retrieves items based on <paramref name="pageIndex"/> and <paramref name="pageSize"/> arguments.
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">
|
||||
/// The zero-based index of the page that corresponds to the items to retrieve.
|
||||
/// </param>
|
||||
/// <param name="pageSize">
|
||||
/// The number of <typeparamref name="TSource"/> items to retrieve for the specified <paramref name="pageIndex"/>.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">
|
||||
/// Used to propagate notification that operation should be canceled.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns a collection of <typeparamref name="TSource"/>.
|
||||
/// </returns>
|
||||
Task<IEnumerable<TSource>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
/// <param name="pageIndex">
|
||||
/// The zero-based index of the page that corresponds to the items to retrieve.
|
||||
/// </param>
|
||||
/// <param name="pageSize">
|
||||
/// The number of <typeparamref name="TSource"/> items to retrieve for the specified <paramref name="pageIndex"/>.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">
|
||||
/// Used to propagate notification that operation should be canceled.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns a collection of <typeparamref name="TSource"/>.
|
||||
/// </returns>
|
||||
Task<IEnumerable<TSource>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
|
|
|
@ -4,16 +4,15 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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
|
||||
{
|
||||
/// <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
|
|
@ -4,33 +4,32 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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>
|
||||
/// Specifies that a given <see cref="ParameterValue"/> also indicates
|
||||
/// whether the method will not return (eg. throw an exception).
|
||||
/// Initializes a new instance of the <see cref="DoesNotReturnIfAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy from the BCL attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal sealed class DoesNotReturnIfAttribute : Attribute
|
||||
/// <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)
|
||||
{
|
||||
/// <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; }
|
||||
ParameterValue = parameterValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter value should be <see langword="true"/>.
|
||||
/// </summary>
|
||||
public bool ParameterValue { get; }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -4,17 +4,16 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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
|
||||
{
|
||||
/// <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
|
|
@ -4,25 +4,24 @@
|
|||
|
||||
#if !NET5_0
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
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
|
||||
{
|
||||
/// <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
|
|
@ -11,215 +11,214 @@ using System.Reflection;
|
|||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with types.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with types.
|
||||
/// The mapping of built-in types to their simple representation.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
private static readonly IReadOnlyDictionary<Type, string> BuiltInTypesMap = new Dictionary<Type, string>
|
||||
{
|
||||
/// <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"
|
||||
};
|
||||
[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();
|
||||
/// <summary>
|
||||
/// A thread-safe mapping of precomputed string representation of types.
|
||||
/// </summary>
|
||||
private static readonly ConditionalWeakTable<Type, string> DisplayNames = new();
|
||||
|
||||
/// <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)
|
||||
/// <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)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
// Primitive types use the keyword name
|
||||
if (BuiltInTypesMap.TryGetValue(type, out string? typeName))
|
||||
{
|
||||
return typeName!;
|
||||
}
|
||||
|
||||
// Array types are displayed as Foo[]
|
||||
if (type.IsArray)
|
||||
{
|
||||
Type? elementType = type.GetElementType()!;
|
||||
int 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())
|
||||
{
|
||||
Type? genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
// Nullable<T> types are displayed as T?
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
{
|
||||
Type[]? 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<,,,,,,,>))
|
||||
{
|
||||
IEnumerable<string>? 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)
|
||||
string[]? tokens = type.Name.Split('`');
|
||||
int genericArgumentsCount = int.Parse(tokens[1]);
|
||||
int typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
|
||||
Type[]? currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
|
||||
IEnumerable<string>? 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)
|
||||
{
|
||||
Type? openDeclaringType = type.DeclaringType!;
|
||||
Type[]? 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)
|
||||
{
|
||||
Type? closedDeclaringType = openDeclaringType.GetGenericTypeDefinition().MakeGenericType(rootGenericArguments);
|
||||
|
||||
return $"{FormatDisplayString(closedDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(openDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{type.Namespace}.{displayName}";
|
||||
return typeName!;
|
||||
}
|
||||
|
||||
// Atomically get or build the display string for the current type.
|
||||
return DisplayNames.GetValue(type, t =>
|
||||
// Array types are displayed as Foo[]
|
||||
if (type.IsArray)
|
||||
{
|
||||
Type? elementType = type.GetElementType()!;
|
||||
int 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())
|
||||
{
|
||||
Type? genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
// Nullable<T> types are displayed as T?
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
{
|
||||
Type[]? 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<,,,,,,,>))
|
||||
{
|
||||
IEnumerable<string>? 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)
|
||||
string[]? tokens = type.Name.Split('`');
|
||||
int genericArgumentsCount = int.Parse(tokens[1]);
|
||||
int typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
|
||||
Type[]? currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
|
||||
IEnumerable<string>? 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)
|
||||
{
|
||||
Type? openDeclaringType = type.DeclaringType!;
|
||||
Type[]? 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)
|
||||
{
|
||||
Type? 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()!;
|
||||
{
|
||||
t = t.GetElementType()!;
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}&";
|
||||
}
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}&";
|
||||
}
|
||||
|
||||
// Pointer types are displayed as T*
|
||||
if (t.IsPointer)
|
||||
{
|
||||
int depth = 0;
|
||||
{
|
||||
int depth = 0;
|
||||
|
||||
// Calculate the pointer indirection level
|
||||
while (t.IsPointer)
|
||||
{
|
||||
depth++;
|
||||
t = t.GetElementType()!;
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}{new string('*', depth)}";
|
||||
{
|
||||
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)
|
||||
{
|
||||
/// <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;
|
||||
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
|
||||
/// <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
|
||||
}
|
||||
|
|
|
@ -7,68 +7,67 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with value types.
|
||||
/// </summary>
|
||||
public static class ValueTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with value types.
|
||||
/// Gets the table of hex characters (doesn't allocate, maps to .text section, see <see href="https://github.com/dotnet/roslyn/pull/24621"/>).
|
||||
/// </summary>
|
||||
public static class ValueTypeExtensions
|
||||
private static ReadOnlySpan<byte> HexCharactersTable => new[]
|
||||
{
|
||||
/// <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
|
||||
/// <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
|
||||
sizeOfT = Unsafe.SizeOf<T>(),
|
||||
bufferSize = (2 * sizeOfT) + 2;
|
||||
char* p = stackalloc char[bufferSize];
|
||||
low = b & 0x0F,
|
||||
high = (b & 0xF0) >> 4;
|
||||
|
||||
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);
|
||||
p[j + 1] = (char)Unsafe.Add(ref rh, low);
|
||||
p[j] = (char)Unsafe.Add(ref rh, high);
|
||||
}
|
||||
|
||||
return new string(p, 0, bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,440 +6,439 @@ using System;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input value is <see langword="default"/>.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
/// <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>
|
||||
{
|
||||
/// <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))
|
||||
{
|
||||
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.ThrowArgumentExceptionForIsDefault(value, name);
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, 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>
|
||||
else if (sizeof(T) == 2)
|
||||
{
|
||||
if (!value.Equals(default))
|
||||
ushort valueUShort = Unsafe.As<T, ushort>(ref value);
|
||||
ushort targetUShort = Unsafe.As<T, ushort>(ref target);
|
||||
|
||||
if (valueUShort == targetUShort)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotDefault<T>(name);
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, 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>
|
||||
else if (sizeof(T) == 4)
|
||||
{
|
||||
if (value.Equals(target))
|
||||
uint valueUInt = Unsafe.As<T, uint>(ref value);
|
||||
uint targetUInt = Unsafe.As<T, uint>(ref target);
|
||||
|
||||
if (valueUInt == targetUInt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsEqualTo(value, target, name);
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(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>
|
||||
else if (sizeof(T) == 8)
|
||||
{
|
||||
if (!value.Equals(target))
|
||||
ulong valueULong = Unsafe.As<T, ulong>(ref value);
|
||||
ulong targetULong = Unsafe.As<T, ulong>(ref target);
|
||||
|
||||
if (Bit64Compare(ref valueULong, ref targetULong))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotEqualTo(value, target, name);
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(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
|
||||
else if (sizeof(T) == 16)
|
||||
{
|
||||
// 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);
|
||||
ulong valueULong0 = Unsafe.As<T, ulong>(ref value);
|
||||
ulong targetULong0 = Unsafe.As<T, ulong>(ref target);
|
||||
|
||||
if (valueByte == targetByte)
|
||||
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 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(Unsafe.AsPointer(ref value), sizeof(T));
|
||||
Span<byte> targetBytes = new(Unsafe.AsPointer(ref target), sizeof(T));
|
||||
|
||||
if (valueBytes.SequenceEqual(targetBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name);
|
||||
}
|
||||
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)
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
Span<byte> valueBytes = new(Unsafe.AsPointer(ref value), sizeof(T));
|
||||
Span<byte> targetBytes = new(Unsafe.AsPointer(ref target), sizeof(T));
|
||||
|
||||
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)
|
||||
if (valueBytes.SequenceEqual(targetBytes))
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,267 +5,266 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input value must be within a given distance from a specified value.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
/// <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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
uint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (uint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (uint)(target - value);
|
||||
}
|
||||
|
||||
if (difference <= delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name);
|
||||
difference = (uint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (uint)(target - value);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
if (difference <= delta)
|
||||
{
|
||||
uint difference;
|
||||
|
||||
if (value >= target)
|
||||
{
|
||||
difference = (uint)(value - target);
|
||||
}
|
||||
else
|
||||
{
|
||||
difference = (uint)(target - value);
|
||||
}
|
||||
|
||||
if (difference > delta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,79 +6,78 @@ using System;
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input <see cref="Stream"/> instance must support reading.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
/// <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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (stream.CanRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForCanRead(stream, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,403 +8,402 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
#pragma warning disable CS8777
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input <see cref="string"/> instance must be <see langword="null"/> or empty.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
/// <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)
|
||||
{
|
||||
/// <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))
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNullOrEmpty(text, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,183 +6,182 @@ using System;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input <see cref="Task"/> instance is in a completed state.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
/// <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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (task.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsCompleted(task, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,346 +7,345 @@ using System.Diagnostics;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Asserts that the input value is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public static partial class Guard
|
||||
/// <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
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentExceptionForIsNull(value, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,49 +5,48 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotEmpty{T}(Span{T},string)"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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}(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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,170 +5,169 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsDefault{T}"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
/// <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
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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.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.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.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="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.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.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.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.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 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.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.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.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.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)}");
|
||||
}
|
||||
/// <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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,107 +5,106 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCloseTo(int,int,uint,string)"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCloseTo(int value, int target, uint delta, string name)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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.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.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.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.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.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.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.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.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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,53 +6,52 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.CanRead"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForCanRead(Stream stream, string name)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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.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.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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,201 +6,200 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNullOrEmpty"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNullOrEmpty(string? text, string name)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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)
|
||||
/// <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)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static Exception GetException(string? text, string name)
|
||||
if (text is null)
|
||||
{
|
||||
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);
|
||||
return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or empty, was null");
|
||||
}
|
||||
|
||||
throw GetException(text, name);
|
||||
return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or empty, was empty", 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);
|
||||
}
|
||||
throw GetException(text, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsNotNullOrWhitespace"/> fails.
|
||||
/// </summary>
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsNotNullOrWhiteSpace(string? text, string 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)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static Exception GetException(string? text, string name)
|
||||
if (text is null)
|
||||
{
|
||||
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);
|
||||
return new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or whitespace, was null");
|
||||
}
|
||||
|
||||
throw GetException(text, name);
|
||||
return new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or whitespace, was whitespace", 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);
|
||||
}
|
||||
throw GetException(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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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,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.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="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="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="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.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)}");
|
||||
}
|
||||
/// <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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,107 +6,106 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Throws an <see cref="ArgumentException"/> when <see cref="Guard.IsCompleted"/> fails.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
[DoesNotReturn]
|
||||
public static void ThrowArgumentExceptionForIsCompleted(Task task, string name)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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.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.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.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.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.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.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.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.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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,199 +6,198 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace CommunityToolkit.Diagnostics
|
||||
namespace CommunityToolkit.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to verify conditions when running code.
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// </summary>
|
||||
public static partial class Guard
|
||||
private static partial class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods to efficiently throw exceptions.
|
||||
/// Returns a formatted representation of the input value.
|
||||
/// </summary>
|
||||
private static partial class ThrowHelper
|
||||
/// <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)
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
return obj switch
|
||||
{
|
||||
string _ => $"\"{obj}\"",
|
||||
null => "null",
|
||||
_ => $"<{obj}>"
|
||||
};
|
||||
}
|
||||
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="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="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="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.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.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.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.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.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.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 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.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.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.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)"/> 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.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)"/> 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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,17 +4,16 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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
|
||||
{
|
||||
/// <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
|
|
@ -4,33 +4,32 @@
|
|||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
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>
|
||||
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter
|
||||
/// will not be null even if the corresponding type allows it.
|
||||
/// Initializes a new instance of the <see cref="NotNullWhenAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>Internal copy of the .NET Standard 2.1 attribute.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
/// <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)
|
||||
{
|
||||
/// <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; }
|
||||
ReturnValue = returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the return value should be <see langword="true"/>.
|
||||
/// </summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -4,25 +4,24 @@
|
|||
|
||||
#if !NET5_0
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
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
|
||||
{
|
||||
/// <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
|
|
@ -8,222 +8,221 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.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>
|
||||
/// 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.
|
||||
/// Initializes a new instance of the <see cref="Box{T}"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value being boxed.</typeparam>
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
public sealed class Box<T>
|
||||
where T : struct
|
||||
/// <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()
|
||||
{
|
||||
// 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 CommunityToolkit.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)}>");
|
||||
}
|
||||
throw new InvalidOperationException("The CommunityToolkit.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>
|
||||
/// Helpers for working with the <see cref="Box{T}"/> type.
|
||||
/// Gets a <typeparamref name="T"/> reference from a <see cref="Box{T}"/> instance.
|
||||
/// </summary>
|
||||
public static class BoxExtensions
|
||||
/// <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
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,247 +11,117 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers.Views;
|
||||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.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>
|
||||
/// Represents a heap-based, array-backed output sink into which <typeparamref name="T"/> data can be written.
|
||||
/// The default buffer size to use to expand empty arrays.
|
||||
/// </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;
|
||||
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 <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>
|
||||
/// 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;
|
||||
/// <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>
|
||||
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="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="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>
|
||||
/// 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();
|
||||
/// <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/>
|
||||
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>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckBufferAndEnsureCapacity(int sizeHint)
|
||||
get
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
|
@ -260,106 +130,235 @@ namespace CommunityToolkit.HighPerformance.Buffers
|
|||
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 $"CommunityToolkit.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");
|
||||
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 $"CommunityToolkit.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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,20 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.HighPerformance.Buffers;
|
||||
|
||||
/// <summary>
|
||||
/// An <see langword="enum"/> that indicates a mode to use when allocating buffers.
|
||||
/// </summary>
|
||||
public enum AllocationMode
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see langword="enum"/> that indicates a mode to use when allocating buffers.
|
||||
/// The default allocation mode for pooled memory (rented buffers are not cleared).
|
||||
/// </summary>
|
||||
public enum AllocationMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The default allocation mode for pooled memory (rented buffers are not cleared).
|
||||
/// </summary>
|
||||
Default,
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Clear pooled buffers when renting them.
|
||||
/// </summary>
|
||||
Clear
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Clear pooled buffers when renting them.
|
||||
/// </summary>
|
||||
Clear
|
||||
}
|
||||
|
|
|
@ -5,46 +5,45 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.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>
|
||||
/// 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.
|
||||
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
|
||||
/// </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; }
|
||||
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 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 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 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>
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
/// <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();
|
||||
}
|
||||
|
|
|
@ -9,48 +9,48 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <typeparamref name="TFrom"/> array, to <typeparamref name="TTo"/> values.
|
||||
/// The source <typeparamref name="TFrom"/> array to read data from.
|
||||
/// </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
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <typeparamref name="TFrom"/> array to read data from.
|
||||
/// </summary>
|
||||
private readonly TFrom[] array;
|
||||
this.array = array;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
|
||||
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
|
||||
|
@ -58,76 +58,75 @@ namespace CommunityToolkit.HighPerformance.Buffers.Internals
|
|||
|
||||
return MemoryMarshal.CreateSpan(ref r1, length);
|
||||
#else
|
||||
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);
|
||||
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);
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,21 +5,20 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces
|
||||
namespace CommunityToolkit.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>
|
||||
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
|
||||
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
/// <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;
|
||||
}
|
||||
|
|
|
@ -9,126 +9,125 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="MemoryManager{T}"/> of <typeparamref name="TFrom"/>, to <typeparamref name="TTo"/> values.
|
||||
/// The source <see cref="MemoryManager{T}"/> to read data from.
|
||||
/// </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
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="MemoryManager{T}"/> to read data from.
|
||||
/// </summary>
|
||||
private readonly MemoryManager<TFrom> memoryManager;
|
||||
this.memoryManager = memoryManager;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see name="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int offset;
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
Span<TFrom> span = this.memoryManager.GetSpan().Slice(this.offset, this.length);
|
||||
|
||||
/// <summary>
|
||||
/// The original used length for <see name="memoryManager"/>.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
return MemoryMarshal.Cast<TFrom, TTo>(span);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
/// <inheritdoc/>
|
||||
public override MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
|
||||
{
|
||||
this.memoryManager = memoryManager;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
ThrowArgumentExceptionForInvalidIndex();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
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>());
|
||||
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()
|
||||
if (remainder != 0)
|
||||
{
|
||||
this.memoryManager.Unpin();
|
||||
ThrowArgumentExceptionForInvalidAlignment();
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,46 +9,46 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="string"/> to <typeparamref name="TTo"/> values.
|
||||
/// The source <see cref="string"/> to read data from.
|
||||
/// </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
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="string"/> to read data from.
|
||||
/// </summary>
|
||||
private readonly string text;
|
||||
this.text = text;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref char r0 = ref this.text.DangerousGetReferenceAt(this.offset);
|
||||
ref TTo r1 = ref Unsafe.As<char, TTo>(ref r0);
|
||||
|
@ -56,70 +56,69 @@ namespace CommunityToolkit.HighPerformance.Buffers.Internals
|
|||
|
||||
return MemoryMarshal.CreateSpan(ref r1, length);
|
||||
#else
|
||||
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
|
||||
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
|
||||
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
|
||||
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
|
||||
|
||||
return MemoryMarshal.Cast<char, TTo>(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,184 +9,183 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using CommunityToolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.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>
|
||||
/// Represents an output sink into which <typeparamref name="T"/> data can be written, backed by a <see cref="Memory{T}"/> instance.
|
||||
/// The underlying <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;
|
||||
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;
|
||||
/// <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;
|
||||
}
|
||||
/// <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>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlyMemory<T> WrittenMemory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ValidateSizeHint(int sizeHint)
|
||||
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)
|
||||
{
|
||||
if (sizeHint < 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
|
||||
}
|
||||
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
sizeHint = 1;
|
||||
}
|
||||
|
||||
if (sizeHint > FreeCapacity)
|
||||
{
|
||||
ThrowArgumentExceptionForCapacityExceeded();
|
||||
}
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeCount();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public override string ToString()
|
||||
if (this.index > this.memory.Length - count)
|
||||
{
|
||||
// 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 $"CommunityToolkit.HighPerformance.Buffers.MemoryBufferWriter<{typeof(T)}>[{this.index}]";
|
||||
ThrowArgumentExceptionForAdvancedTooFar();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
|
||||
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)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
|
||||
ThrowArgumentOutOfRangeExceptionForNegativeSizeHint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
|
||||
if (sizeHint == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
|
||||
sizeHint = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
private static void ThrowArgumentExceptionForAdvancedTooFar()
|
||||
if (sizeHint > FreeCapacity)
|
||||
{
|
||||
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");
|
||||
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 $"CommunityToolkit.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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,175 +12,175 @@ using System.Runtime.InteropServices;
|
|||
#endif
|
||||
using CommunityToolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.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>
|
||||
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and a fast <see cref="Span{T}"/> accessor.
|
||||
/// The starting offset within <see cref="array"/>.
|
||||
/// </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;
|
||||
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;
|
||||
/// <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 <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>
|
||||
/// 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)
|
||||
/// <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.start = 0;
|
||||
this.length = length;
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(length);
|
||||
this.array.AsSpan(0, length).Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == AllocationMode.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)
|
||||
{
|
||||
this.array.AsSpan(0, length).Clear();
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return new Memory<T>(array!, this.start, this.length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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]
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
||||
/// </summary>
|
||||
public Span<T> Span
|
||||
{
|
||||
[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
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.length;
|
||||
}
|
||||
T[]? array = this.array;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Memory<T> Memory
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
if (array is null)
|
||||
{
|
||||
T[]? array = this.array;
|
||||
|
||||
if (array is null)
|
||||
{
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
|
||||
return new Memory<T>(array!, this.start, this.length);
|
||||
ThrowObjectDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 NETCOREAPP3_1_OR_GREATER
|
||||
ref T r0 = ref array!.DangerousGetReferenceAt(this.start);
|
||||
|
@ -196,160 +196,159 @@ namespace CommunityToolkit.HighPerformance.Buffers
|
|||
// 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);
|
||||
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 $"CommunityToolkit.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 $"CommunityToolkit.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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,200 +12,199 @@ using System.Runtime.InteropServices;
|
|||
#endif
|
||||
using CommunityToolkit.HighPerformance.Buffers.Views;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers
|
||||
namespace CommunityToolkit.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>
|
||||
{
|
||||
/// <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;
|
||||
/// <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 <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>
|
||||
/// 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)
|
||||
/// <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.length = length;
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(length);
|
||||
|
||||
if (mode == AllocationMode.Clear)
|
||||
{
|
||||
this.array.AsSpan(0, length).Clear();
|
||||
}
|
||||
this.array.AsSpan(0, length).Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public static SpanOwner<T> Empty
|
||||
/// <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
|
||||
{
|
||||
[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 NETCOREAPP3_1_OR_GREATER
|
||||
ref T r0 = ref array!.DangerousGetReference();
|
||||
|
||||
return MemoryMarshal.CreateSpan(ref r0, this.length);
|
||||
#else
|
||||
return new Span<T>(this.array, 0, this.length);
|
||||
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 $"CommunityToolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the first element within the current instance, with no bounds check.
|
||||
/// </summary>
|
||||
/// <returns>A reference to the first element within the current instance.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref T DangerousGetReference()
|
||||
{
|
||||
return ref 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 $"CommunityToolkit.HighPerformance.Buffers.SpanOwner<{typeof(T)}>[{this.length}]";
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,54 +4,53 @@
|
|||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Buffers.Views
|
||||
namespace CommunityToolkit.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>
|
||||
/// A debug proxy used to display items in a 1D layout.
|
||||
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to display.</typeparam>
|
||||
internal sealed class MemoryDebugView<T>
|
||||
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
|
||||
public MemoryDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
|
||||
{
|
||||
/// <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; }
|
||||
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; }
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ using CommunityToolkit.HighPerformance.Memory.Internals;
|
|||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
{
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
|
||||
|
@ -29,27 +29,27 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
/// <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 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 initial offset within <see cref="instance"/>.
|
||||
/// </summary>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <summary>
|
||||
/// The total available length for the sequence.
|
||||
/// </summary>
|
||||
private readonly int length;
|
||||
/// <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;
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -103,65 +103,65 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
}
|
||||
/// <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)]
|
||||
/// <summary>
|
||||
/// Gets the total available length for the sequence.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
get => this.span.Length;
|
||||
#else
|
||||
get => this.length;
|
||||
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]
|
||||
/// <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
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
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);
|
||||
nint offset = (nint)(uint)index * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -179,27 +179,27 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return new Enumerator(this.span, this.step);
|
||||
#else
|
||||
return new Enumerator(this.instance, this.offset, this.length, this.step);
|
||||
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)
|
||||
{
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.step == 1)
|
||||
{
|
||||
|
@ -221,57 +221,57 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
int
|
||||
sourceLength = this.span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
int
|
||||
sourceLength = this.length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.step == 1)
|
||||
{
|
||||
|
@ -283,105 +283,105 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
int length = this.length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
|
||||
[Pure]
|
||||
public T[] ToArray()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
int length = this.span.Length;
|
||||
#else
|
||||
int length = this.length;
|
||||
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;
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Span, enumerable.Step);
|
||||
#else
|
||||
return new ReadOnlyRefEnumerable<T>(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
|
||||
private readonly object? instance;
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
|
||||
private readonly IntPtr offset;
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
|
||||
private readonly int length;
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
|
||||
private readonly int step;
|
||||
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -397,76 +397,75 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
}
|
||||
/// <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()
|
||||
{
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref readonly T Current
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref readonly T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
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);
|
||||
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,62 +9,62 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// The source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct ReadOnlySpanEnumerable<T>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
this.span = span;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current index within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private int index;
|
||||
/// <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>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlySpanEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
|
||||
/// <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)]
|
||||
public ReadOnlySpanEnumerable(ReadOnlySpan<T> span)
|
||||
get
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
@ -72,21 +72,21 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
// See comment in SpanEnumerable<T> about this
|
||||
return new Item(ref ri, this.index);
|
||||
#else
|
||||
return new Item(this.span, this.index);
|
||||
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>
|
||||
/// An item from a source <see cref="Span{T}"/> instance.
|
||||
/// The source <see cref="ReadOnlySpan{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;
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -100,58 +100,57 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
this.span = MemoryMarshal.CreateReadOnlySpan(ref value, index);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// The current index within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private readonly int index;
|
||||
/// <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;
|
||||
}
|
||||
/// <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
|
||||
/// <summary>
|
||||
/// Gets the reference to the current value.
|
||||
/// </summary>
|
||||
public ref readonly T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
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);
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
return this.index;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,108 +8,107 @@ using System.ComponentModel;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// The source <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>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="ReadOnlySpan{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly ReadOnlySpan<T> span;
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The separator item to use.
|
||||
/// </summary>
|
||||
private readonly T separator;
|
||||
/// <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>
|
||||
/// The current initial offset.
|
||||
/// </summary>
|
||||
private int start;
|
||||
/// <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;
|
||||
|
||||
/// <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)
|
||||
// Additional check if the separator is not the last character
|
||||
if (newEnd <= length)
|
||||
{
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
this.start = newEnd;
|
||||
|
||||
/// <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;
|
||||
// We need to call this extension explicitly or the extension method resolution rules for the C# compiler
|
||||
// will end up picking CommunityToolkit.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
|
||||
// CommunityToolkit.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);
|
||||
|
||||
/// <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)
|
||||
// Extract the current subsequence
|
||||
if (index >= 0)
|
||||
{
|
||||
this.start = newEnd;
|
||||
|
||||
// We need to call this extension explicitly or the extension method resolution rules for the C# compiler
|
||||
// will end up picking CommunityToolkit.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
|
||||
// CommunityToolkit.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;
|
||||
this.end = newEnd + index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.end = length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ using CommunityToolkit.HighPerformance.Memory.Internals;
|
|||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
{
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
|
||||
|
@ -29,22 +29,22 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
/// <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 target <see cref="object"/> instance, if present.
|
||||
/// </summary>
|
||||
internal readonly object? Instance;
|
||||
|
||||
/// <summary>
|
||||
/// The initial offset within <see cref="Instance"/>.
|
||||
/// </summary>
|
||||
internal readonly IntPtr Offset;
|
||||
/// <summary>
|
||||
/// The 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;
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -86,65 +86,65 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
}
|
||||
/// <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)]
|
||||
/// <summary>
|
||||
/// Gets the total available length for the sequence.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
get => this.Span.Length;
|
||||
#else
|
||||
get;
|
||||
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]
|
||||
/// <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
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
if ((uint)index >= (uint)Length)
|
||||
{
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.Span);
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
|
||||
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);
|
||||
nint offset = (nint)(uint)index * (nint)(uint)this.Step;
|
||||
ref T ri = ref Unsafe.Add(ref r0, offset);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -162,23 +162,23 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return new Enumerator(this.Span, this.Step);
|
||||
#else
|
||||
return new Enumerator(this.Instance, this.Offset, this.Length, this.Step);
|
||||
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()
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the contents of the current <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
// Fast path for contiguous items
|
||||
if (this.Step == 1)
|
||||
|
@ -191,22 +191,22 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.Step == 1)
|
||||
{
|
||||
|
@ -228,57 +228,57 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
int
|
||||
sourceLength = this.Span.Length,
|
||||
destinationLength = destination.Span.Length;
|
||||
#else
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
int
|
||||
sourceLength = this.Length,
|
||||
destinationLength = destination.Length;
|
||||
#endif
|
||||
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
if (destinationLength >= sourceLength)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.Step == 1)
|
||||
{
|
||||
|
@ -290,51 +290,51 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
if ((uint)destination.Length < (uint)length)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
if (destination.Length >= length)
|
||||
{
|
||||
CopyTo(destination);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.Step == 1)
|
||||
{
|
||||
|
@ -346,49 +346,49 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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;
|
||||
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);
|
||||
if ((uint)destinationLength < (uint)sourceLength)
|
||||
{
|
||||
ThrowArgumentExceptionForDestinationTooShort();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
int length = this.Length;
|
||||
#endif
|
||||
|
||||
if (length >= source.Length)
|
||||
{
|
||||
CopyFrom(source);
|
||||
if (length >= source.Length)
|
||||
{
|
||||
CopyFrom(source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
if (this.Step == 1)
|
||||
{
|
||||
|
@ -400,69 +400,69 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
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);
|
||||
}
|
||||
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()
|
||||
{
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
int length = this.Span.Length;
|
||||
#else
|
||||
int length = this.Length;
|
||||
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;
|
||||
// Empty array if no data is mapped
|
||||
if (length == 0)
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom enumerator type to traverse items within a <see cref="RefEnumerable{T}"/> instance.
|
||||
/// </summary>
|
||||
public ref struct Enumerator
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Span"/>
|
||||
private readonly Span<T> span;
|
||||
#else
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
|
||||
private readonly object? instance;
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
|
||||
private readonly object? instance;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
|
||||
private readonly IntPtr offset;
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
|
||||
private readonly IntPtr offset;
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
|
||||
private readonly int length;
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
|
||||
private readonly int length;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
|
||||
private readonly int step;
|
||||
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
|
||||
private readonly int step;
|
||||
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
/// <summary>
|
||||
/// The current position in the sequence.
|
||||
/// </summary>
|
||||
private int position;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -478,83 +478,82 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
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;
|
||||
}
|
||||
/// <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()
|
||||
{
|
||||
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return ++this.position < this.span.Length;
|
||||
#else
|
||||
return ++this.position < this.length;
|
||||
return ++this.position < this.length;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref T Current
|
||||
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
|
||||
public readonly ref T Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref this.span.DangerousGetReference();
|
||||
#else
|
||||
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,62 +9,62 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="Span{T}"/> instance.
|
||||
/// The source <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to enumerate.</typeparam>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ref struct SpanEnumerable<T>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly Span<T> span;
|
||||
this.span = span;
|
||||
this.index = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current index within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private int index;
|
||||
/// <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>
|
||||
/// Initializes a new instance of the <see cref="SpanEnumerable{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
|
||||
/// <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)]
|
||||
public SpanEnumerable(Span<T> span)
|
||||
get
|
||||
{
|
||||
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 NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
@ -77,21 +77,21 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
// 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);
|
||||
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>
|
||||
/// An item from a source <see cref="Span{T}"/> instance.
|
||||
/// The 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;
|
||||
private readonly Span<T> span;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -105,58 +105,57 @@ namespace CommunityToolkit.HighPerformance.Enumerables
|
|||
this.span = MemoryMarshal.CreateSpan(ref value, index);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// The current index within <see cref="span"/>.
|
||||
/// </summary>
|
||||
private readonly int index;
|
||||
/// <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;
|
||||
}
|
||||
/// <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
|
||||
/// <summary>
|
||||
/// Gets the reference to the current value.
|
||||
/// </summary>
|
||||
public ref T Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
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);
|
||||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
|
||||
|
||||
return ref ri;
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
/// <summary>
|
||||
/// Gets the current index.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
return this.span.Length;
|
||||
#else
|
||||
return this.index;
|
||||
return this.index;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,99 +8,98 @@ using System.ComponentModel;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Enumerables
|
||||
namespace CommunityToolkit.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>
|
||||
/// A <see langword="ref"/> <see langword="struct"/> that tokenizes a given <see cref="Span{T}"/> instance.
|
||||
/// The source <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>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The source <see cref="Span{T}"/> instance.
|
||||
/// </summary>
|
||||
private readonly Span<T> span;
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The separator item to use.
|
||||
/// </summary>
|
||||
private readonly T separator;
|
||||
/// <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>
|
||||
/// The current initial offset.
|
||||
/// </summary>
|
||||
private int start;
|
||||
/// <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;
|
||||
|
||||
/// <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)
|
||||
// Additional check if the separator is not the last character
|
||||
if (newEnd <= length)
|
||||
{
|
||||
this.span = span;
|
||||
this.separator = separator;
|
||||
this.start = 0;
|
||||
this.end = -1;
|
||||
}
|
||||
this.start = newEnd;
|
||||
|
||||
/// <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;
|
||||
int index = this.span.Slice(newEnd).IndexOf(this.separator);
|
||||
|
||||
/// <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)
|
||||
// Extract the current subsequence
|
||||
if (index >= 0)
|
||||
{
|
||||
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;
|
||||
this.end = newEnd + index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.end = length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,24 +15,24 @@ using CommunityToolkit.HighPerformance.Helpers;
|
|||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// Returns a reference to the first element within a given <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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 NETCOREAPP3_1
|
||||
|
@ -41,24 +41,24 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
return ref r0;
|
||||
#else
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
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)
|
||||
{
|
||||
/// <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);
|
||||
|
@ -71,13 +71,13 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
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);
|
||||
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;
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
// Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
|
@ -99,121 +99,120 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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>
|
||||
/// <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)
|
||||
{
|
||||
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;
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,54 +14,54 @@ using CommunityToolkit.HighPerformance.Helpers;
|
|||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// Returns a reference to the first element within a given 2D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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 NETCOREAPP3_1
|
||||
RawArray2DData? 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>();
|
||||
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
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)
|
||||
{
|
||||
/// <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 NETCOREAPP3_1
|
||||
RawArray2DData? arrayData = Unsafe.As<RawArray2DData>(array)!;
|
||||
nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
|
||||
|
@ -70,15 +70,15 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
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);
|
||||
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;
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
|
||||
|
@ -105,172 +105,172 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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)
|
||||
/// <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())
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
int height = array.GetLength(0);
|
||||
|
||||
if ((uint)row >= (uint)height)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
if ((uint)row >= (uint)height)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForRow();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
int width = array.GetLength(1);
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
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);
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, width, 1);
|
||||
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();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
int width = array.GetLength(1);
|
||||
|
||||
if ((uint)column >= (uint)width)
|
||||
{
|
||||
if (array.IsCovariant())
|
||||
{
|
||||
ThrowArrayTypeMismatchException();
|
||||
}
|
||||
ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
int width = array.GetLength(1);
|
||||
|
||||
if ((uint)column >= (uint)width)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForColumn();
|
||||
}
|
||||
|
||||
int height = array.GetLength(0);
|
||||
int height = array.GetLength(0);
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
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);
|
||||
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
|
||||
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
|
||||
|
||||
return new RefEnumerable<T>(array, offset, height, width);
|
||||
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>
|
||||
/// <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="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>
|
||||
/// <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);
|
||||
}
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -383,85 +383,84 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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>
|
||||
/// <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)
|
||||
{
|
||||
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;
|
||||
ThrowOverflowException();
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,55 +13,55 @@ using CommunityToolkit.HighPerformance.Buffers.Internals;
|
|||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Array"/> type.
|
||||
/// Returns a reference to the first element within a given 3D <typeparamref name="T"/> array, with no bounds checks.
|
||||
/// </summary>
|
||||
public static partial class ArrayExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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 NETCOREAPP3_1
|
||||
RawArray3DData? 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>();
|
||||
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset);
|
||||
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)
|
||||
{
|
||||
/// <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 NETCOREAPP3_1
|
||||
RawArray3DData? arrayData = Unsafe.As<RawArray3DData>(array)!;
|
||||
nint offset =
|
||||
|
@ -72,19 +72,19 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
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);
|
||||
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;
|
||||
return ref ri;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
// See description for this in the 2D partial file.
|
||||
|
@ -218,105 +218,104 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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");
|
||||
}
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,24 +8,23 @@ using System.Runtime.CompilerServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers;
|
||||
using CommunityToolkit.HighPerformance.Streams;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ArrayPoolBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ArrayPoolBufferWriterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ArrayPoolBufferWriter{T}"/> type.
|
||||
/// 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>
|
||||
public static class ArrayPoolBufferWriterExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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));
|
||||
}
|
||||
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(writer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,52 +6,51 @@ using System;
|
|||
using System.Buffers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ArrayPool{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ArrayPoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ArrayPool{T}"/> type.
|
||||
/// Changes the number of elements of a rented one-dimensional array to the specified new size.
|
||||
/// </summary>
|
||||
public static class ArrayPoolExtensions
|
||||
/// <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, [NotNull] ref T[]? array, int newSize, bool clearArray = false)
|
||||
{
|
||||
/// <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, [NotNull] 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)
|
||||
{
|
||||
// If the old array is null, just create a new one with the requested size
|
||||
if (array is null)
|
||||
{
|
||||
array = pool.Rent(newSize);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,78 +6,77 @@ using System;
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="bool"/> type.
|
||||
/// </summary>
|
||||
public static class BoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="bool"/> type.
|
||||
/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
|
||||
/// </summary>
|
||||
public static class BoolExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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;
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,126 +11,125 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Buffers;
|
||||
using CommunityToolkit.HighPerformance.Streams;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IBufferWriter{T}"/> type.
|
||||
/// </summary>
|
||||
public static class IBufferWriterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IBufferWriter{T}"/> type.
|
||||
/// 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>
|
||||
public static class IBufferWriterExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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 (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.
|
||||
ArrayPoolBufferWriter<byte>? internalWriter = Unsafe.As<ArrayPoolBufferWriter<byte>>(writer)!;
|
||||
// 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.
|
||||
ArrayPoolBufferWriter<byte>? internalWriter = Unsafe.As<ArrayPoolBufferWriter<byte>>(writer)!;
|
||||
|
||||
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(internalWriter));
|
||||
}
|
||||
|
||||
return new IBufferWriterStream<IBufferWriterOwner>(new IBufferWriterOwner(writer));
|
||||
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(internalWriter));
|
||||
}
|
||||
|
||||
/// <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
|
||||
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)
|
||||
{
|
||||
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);
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
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)
|
||||
{
|
||||
Span<T> span = writer.GetSpan(1);
|
||||
|
||||
if (span.Length < 1)
|
||||
{
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
MemoryMarshal.GetReference(span) = value;
|
||||
|
||||
writer.Advance(1);
|
||||
ThrowArgumentExceptionForEndOfBuffer();
|
||||
}
|
||||
|
||||
/// <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);
|
||||
MemoryMarshal.GetReference(span) = value;
|
||||
|
||||
source.CopyTo(destination);
|
||||
writer.Advance(1);
|
||||
}
|
||||
|
||||
writer.Advance(source.Length);
|
||||
}
|
||||
/// <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 !NETSTANDARD2_1_OR_GREATER
|
||||
/// <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);
|
||||
/// <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);
|
||||
span.CopyTo(destination);
|
||||
|
||||
writer.Advance(span.Length);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
/// <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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,28 +9,27 @@ using System.IO;
|
|||
using System.Runtime.CompilerServices;
|
||||
using MemoryStream = CommunityToolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IMemoryOwner{T}"/> type.
|
||||
/// </summary>
|
||||
public static class IMemoryOwnerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="IMemoryOwner{T}"/> type.
|
||||
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
|
||||
/// </summary>
|
||||
public static class IMemoryOwnerExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
return MemoryStream.Create(memoryOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using MemoryStream = CommunityToolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Memory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class MemoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Memory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class MemoryExtensions
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
|
||||
|
@ -62,58 +62,57 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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);
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ using CommunityToolkit.HighPerformance.Buffers.Internals;
|
|||
using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces;
|
||||
using MemoryStream = CommunityToolkit.HighPerformance.Streams.MemoryStream;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlyMemory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ReadOnlyMemoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlyMemory{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ReadOnlyMemoryExtensions
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
|
||||
|
@ -65,94 +65,93 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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 MemoryManager<TFrom>? 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);
|
||||
}
|
||||
/// <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 MemoryManager<TFrom>? 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,160 +9,160 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Enumerables;
|
||||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
|
||||
/// </summary>
|
||||
public static class ReadOnlySpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="ReadOnlySpan{T}"/> type.
|
||||
/// Returns a reference to the first element within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
public static class ReadOnlySpanExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
return ref r1;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -210,194 +210,193 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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)
|
||||
/// <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)
|
||||
{
|
||||
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;
|
||||
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,62 +9,62 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Enumerables;
|
||||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Span{T}"/> type.
|
||||
/// </summary>
|
||||
public static class SpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Span{T}"/> type.
|
||||
/// Returns a reference to the first element within a given <see cref="Span{T}"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
public static class SpanExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -112,190 +112,189 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
#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");
|
||||
}
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,76 +6,76 @@ using System.ComponentModel;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="SpinLock"/> type.
|
||||
/// </summary>
|
||||
public static class SpinLockExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="SpinLock"/> type.
|
||||
/// 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>
|
||||
public static class SpinLockExtensions
|
||||
/// <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>
|
||||
/// 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.
|
||||
/// The <see cref="SpinLock"/>* pointer to the target <see cref="SpinLock"/> value to use.
|
||||
/// </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>
|
||||
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 static unsafe UnsafeLock Enter(SpinLock* spinLock)
|
||||
public UnsafeLock(SpinLock* spinLock)
|
||||
{
|
||||
return new(spinLock);
|
||||
this.spinLock = spinLock;
|
||||
this.lockTaken = false;
|
||||
|
||||
spinLock->Enter(ref this.lockTaken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
|
||||
/// Implements the duck-typed <see cref="System.IDisposable.Dispose"/> method and releases the current <see cref="SpinLock"/> instance.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly unsafe ref struct UnsafeLock
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
/// <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)
|
||||
if (this.lockTaken)
|
||||
{
|
||||
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();
|
||||
}
|
||||
this.spinLock->Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -100,48 +100,48 @@ namespace CommunityToolkit.HighPerformance
|
|||
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);
|
||||
}
|
||||
/// <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>
|
||||
/// A <see langword="struct"/> that is used to enter and hold a <see cref="SpinLock"/> through a <see langword="using"/> block or statement.
|
||||
/// The <see cref="Ref{T}"/> instance pointing to the target <see cref="SpinLock"/> value to use.
|
||||
/// </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;
|
||||
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;
|
||||
/// <summary>
|
||||
/// A value indicating whether or not the lock is taken by this <see cref="Lock"/> instance.
|
||||
/// </summary>
|
||||
private readonly bool lockTaken;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
|
@ -157,32 +157,31 @@ namespace CommunityToolkit.HighPerformance
|
|||
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;
|
||||
/// <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);
|
||||
}
|
||||
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()
|
||||
/// <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)
|
||||
{
|
||||
if (this.lockTaken)
|
||||
{
|
||||
this.spinLock.Value.Exit();
|
||||
}
|
||||
this.spinLock.Value.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,121 +12,52 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Stream"/> type.
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="Stream"/> type.
|
||||
/// </summary>
|
||||
public static class StreamExtensions
|
||||
{
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
/// <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)
|
||||
/// <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)
|
||||
{
|
||||
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));
|
||||
return new ValueTask<int>(Task.FromCanceled<int>(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 the memory wraps an array, extract it and use it directly
|
||||
if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment))
|
||||
{
|
||||
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));
|
||||
return new ValueTask<int>(stream.ReadAsync(segment.Array!, segment.Offset, segment.Count, 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)
|
||||
// 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 = stream.Read(rent, 0, buffer.Length);
|
||||
int bytesRead = await stream.ReadAsync(rent, 0, buffer.Length, cancellationToken);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer);
|
||||
rent.AsSpan(0, bytesRead).CopyTo(buffer.Span);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
|
@ -137,41 +68,110 @@ namespace CommunityToolkit.HighPerformance
|
|||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
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.CopyTo(rent);
|
||||
buffer.Span.CopyTo(rent);
|
||||
|
||||
stream.Write(rent, 0, buffer.Length);
|
||||
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>
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public static T Read<T>(this Stream stream)
|
||||
where T : unmanaged
|
||||
{
|
||||
public static T Read<T>(this Stream stream)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
T result = default;
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
|
@ -186,37 +186,37 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
return result;
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
if (stream.Read(buffer, 0, length) != length)
|
||||
{
|
||||
if (stream.Read(buffer, 0, length) != length)
|
||||
{
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
ThrowInvalidOperationExceptionForEndOfStream();
|
||||
}
|
||||
|
||||
return Unsafe.ReadUnaligned<T>(ref buffer[0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
#endif
|
||||
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>
|
||||
/// <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 NETSTANDARD2_1_OR_GREATER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public static void Write<T>(this Stream stream, in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
public static void Write<T>(this Stream stream, in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
ref T r0 = ref Unsafe.AsRef(value);
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref r0);
|
||||
|
@ -226,28 +226,27 @@ namespace CommunityToolkit.HighPerformance
|
|||
|
||||
stream.Write(span);
|
||||
#else
|
||||
int length = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
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()
|
||||
try
|
||||
{
|
||||
throw new InvalidOperationException("The stream didn't contain enough data to read the requested item");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,131 +11,130 @@ using System.Runtime.InteropServices;
|
|||
using CommunityToolkit.HighPerformance.Enumerables;
|
||||
using CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance
|
||||
namespace CommunityToolkit.HighPerformance;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="string"/> type.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with the <see cref="string"/> type.
|
||||
/// Returns a reference to the first element within a given <see cref="string"/>, with no bounds checks.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
/// <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)
|
||||
{
|
||||
/// <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());
|
||||
#else
|
||||
return ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
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)
|
||||
{
|
||||
/// <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());
|
||||
#else
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
|
||||
#endif
|
||||
ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
|
||||
|
||||
return ref ri;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
return ref ri;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,234 +8,234 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to perform bit operations on numeric types.
|
||||
/// </summary>
|
||||
public static class BitHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to perform bit operations on numeric types.
|
||||
/// Checks whether or not a given bit is set.
|
||||
/// </summary>
|
||||
public static class BitHelper
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
/// <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;
|
||||
}
|
||||
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.
|
||||
/// 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 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.
|
||||
/// </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>
|
||||
/// 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 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;
|
||||
/// <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;
|
||||
// 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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
|
@ -243,46 +243,46 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1u << length) - 1u);
|
||||
}
|
||||
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 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;
|
||||
/// <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)
|
||||
|
@ -291,121 +291,121 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
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);
|
||||
/// <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;
|
||||
}
|
||||
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;
|
||||
/// <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;
|
||||
}
|
||||
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 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;
|
||||
/// <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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
|
@ -413,46 +413,46 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#endif
|
||||
|
||||
return (value >> start) & ((1ul << length) - 1ul);
|
||||
}
|
||||
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 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;
|
||||
/// <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)
|
||||
|
@ -461,7 +461,6 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#endif
|
||||
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
return (~loadMask & value) | storeMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,36 +8,35 @@ using System.Runtime.CompilerServices;
|
|||
using static System.Numerics.BitOperations;
|
||||
#endif
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// Utility methods for intrinsic bit-twiddling operations. The methods use hardware intrinsics
|
||||
/// when available on the underlying platform, otherwise they use optimized software fallbacks.
|
||||
/// Rounds up an <see cref="int"/> value to a power of 2.
|
||||
/// </summary>
|
||||
internal static class BitOperations
|
||||
/// <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)
|
||||
{
|
||||
/// <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++;
|
||||
x--;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x++;
|
||||
|
||||
return x;
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,264 +4,263 @@
|
|||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference with a given step.
|
||||
/// </summary>
|
||||
internal static class RefEnumerableHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference with a given step.
|
||||
/// Clears a target memory area.
|
||||
/// </summary>
|
||||
internal static class RefEnumerableHelper
|
||||
/// <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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
nint offset = 0;
|
||||
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!;
|
||||
|
||||
// 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;
|
||||
}
|
||||
length -= 8;
|
||||
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)
|
||||
if (length >= 4)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
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!;
|
||||
|
||||
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;
|
||||
}
|
||||
length -= 4;
|
||||
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>
|
||||
/// <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)
|
||||
// Clear the remaining values
|
||||
while (length > 0)
|
||||
{
|
||||
nint
|
||||
sourceOffset = 0,
|
||||
destinationOffset = 0;
|
||||
Unsafe.Add(ref r0, offset) = default!;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,291 +14,290 @@ using System.Reflection;
|
|||
#endif
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// 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.
|
||||
/// Converts a length of items from one size to another (rounding towards zero).
|
||||
/// </summary>
|
||||
internal static class RuntimeHelpers
|
||||
/// <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
|
||||
{
|
||||
/// <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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
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;
|
||||
// .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)
|
||||
{
|
||||
/// <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;
|
||||
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 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 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;
|
||||
}
|
||||
/// <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 !NETSTANDARD2_1_OR_GREATER
|
||||
/// <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)
|
||||
/// <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)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return (IntPtr)Unsafe.AsPointer(ref data);
|
||||
}
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(obj, ref data);
|
||||
return (IntPtr)Unsafe.AsPointer(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 ObjectMarshal.DangerousGetObjectDataByteOffset(obj, ref data);
|
||||
}
|
||||
|
||||
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(obj, offset);
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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 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 TypeInfo<T>.IsReferenceOrContainsReferences;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
if (!type.GetTypeInfo().IsValueType)
|
||||
{
|
||||
// Common case, for primitive types
|
||||
if (type.GetTypeInfo().IsPrimitive)
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!type.GetTypeInfo().IsValueType)
|
||||
if (IsReferenceOrContainsReferences(field.FieldType))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 !NETSTANDARD2_1_OR_GREATER
|
||||
/// <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>
|
||||
/// A private generic class to preload type info for arbitrary runtime types.
|
||||
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to load info for.</typeparam>
|
||||
private static class TypeInfo<T>
|
||||
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
|
||||
[Pure]
|
||||
private static IntPtr MeasureArrayDataByteOffset()
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
|
||||
T[]? array = new T[1];
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
|
||||
/// </summary>
|
||||
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
|
||||
/// <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()
|
||||
{
|
||||
T[,]? array = new T[1, 1];
|
||||
|
||||
#if !NETSTANDARD2_1_OR_GREATER
|
||||
/// <summary>
|
||||
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
|
||||
/// </summary>
|
||||
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
|
||||
#endif
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0]);
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
T[]? array = new T[1];
|
||||
/// <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()
|
||||
{
|
||||
T[,,]? array = new T[1, 1, 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()
|
||||
{
|
||||
T[,]? 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()
|
||||
{
|
||||
T[,,]? array = new T[1, 1, 1];
|
||||
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0, 0]);
|
||||
}
|
||||
return ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array[0, 0, 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,362 +7,361 @@ using System.Diagnostics.Contracts;
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// Counts the number of occurrences of a given value into a target search space.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
/// <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>
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
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>
|
||||
// Special vectorized version when using a supported type
|
||||
if (typeof(T) == typeof(byte) ||
|
||||
typeof(T) == typeof(sbyte) ||
|
||||
typeof(T) == typeof(bool))
|
||||
{
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
|
||||
sbyte target = Unsafe.As<T, sbyte>(ref value);
|
||||
|
||||
// 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;
|
||||
return CountSimd(ref r1, length, target);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
if (typeof(T) == typeof(char) ||
|
||||
typeof(T) == typeof(ushort) ||
|
||||
typeof(T) == typeof(short))
|
||||
{
|
||||
nint
|
||||
result = 0,
|
||||
offset = 0;
|
||||
ref short r1 = ref Unsafe.As<T, short>(ref r0);
|
||||
short target = Unsafe.As<T, short>(ref value);
|
||||
|
||||
// Skip the initialization overhead if there are not enough items
|
||||
if (length >= Vector<T>.Count)
|
||||
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)
|
||||
{
|
||||
Vector<T> vc = new(value);
|
||||
|
||||
do
|
||||
{
|
||||
Vector<T> vc = new(value);
|
||||
// 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;
|
||||
|
||||
do
|
||||
Vector<T> 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))
|
||||
{
|
||||
// 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;
|
||||
|
||||
Vector<T> 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)
|
||||
{
|
||||
while (chunkLength >= Vector<T>.Count * 8)
|
||||
{
|
||||
ref T ri0 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 0));
|
||||
Vector<T> vi0 = Unsafe.As<T, Vector<T>>(ref ri0);
|
||||
Vector<T> ve0 = Vector.Equals(vi0, vc);
|
||||
ref T ri0 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 0));
|
||||
Vector<T> vi0 = Unsafe.As<T, Vector<T>>(ref ri0);
|
||||
Vector<T> ve0 = Vector.Equals(vi0, vc);
|
||||
|
||||
partials -= ve0;
|
||||
partials -= ve0;
|
||||
|
||||
ref T ri1 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 1));
|
||||
Vector<T> vi1 = Unsafe.As<T, Vector<T>>(ref ri1);
|
||||
Vector<T> ve1 = Vector.Equals(vi1, vc);
|
||||
ref T ri1 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 1));
|
||||
Vector<T> vi1 = Unsafe.As<T, Vector<T>>(ref ri1);
|
||||
Vector<T> ve1 = Vector.Equals(vi1, vc);
|
||||
|
||||
partials -= ve1;
|
||||
partials -= ve1;
|
||||
|
||||
ref T ri2 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 2));
|
||||
Vector<T> vi2 = Unsafe.As<T, Vector<T>>(ref ri2);
|
||||
Vector<T> ve2 = Vector.Equals(vi2, vc);
|
||||
ref T ri2 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 2));
|
||||
Vector<T> vi2 = Unsafe.As<T, Vector<T>>(ref ri2);
|
||||
Vector<T> ve2 = Vector.Equals(vi2, vc);
|
||||
|
||||
partials -= ve2;
|
||||
partials -= ve2;
|
||||
|
||||
ref T ri3 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 3));
|
||||
Vector<T> vi3 = Unsafe.As<T, Vector<T>>(ref ri3);
|
||||
Vector<T> ve3 = Vector.Equals(vi3, vc);
|
||||
ref T ri3 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 3));
|
||||
Vector<T> vi3 = Unsafe.As<T, Vector<T>>(ref ri3);
|
||||
Vector<T> ve3 = Vector.Equals(vi3, vc);
|
||||
|
||||
partials -= ve3;
|
||||
partials -= ve3;
|
||||
|
||||
ref T ri4 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 4));
|
||||
Vector<T> vi4 = Unsafe.As<T, Vector<T>>(ref ri4);
|
||||
Vector<T> ve4 = Vector.Equals(vi4, vc);
|
||||
ref T ri4 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 4));
|
||||
Vector<T> vi4 = Unsafe.As<T, Vector<T>>(ref ri4);
|
||||
Vector<T> ve4 = Vector.Equals(vi4, vc);
|
||||
|
||||
partials -= ve4;
|
||||
partials -= ve4;
|
||||
|
||||
ref T ri5 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 5));
|
||||
Vector<T> vi5 = Unsafe.As<T, Vector<T>>(ref ri5);
|
||||
Vector<T> ve5 = Vector.Equals(vi5, vc);
|
||||
ref T ri5 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 5));
|
||||
Vector<T> vi5 = Unsafe.As<T, Vector<T>>(ref ri5);
|
||||
Vector<T> ve5 = Vector.Equals(vi5, vc);
|
||||
|
||||
partials -= ve5;
|
||||
partials -= ve5;
|
||||
|
||||
ref T ri6 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 6));
|
||||
Vector<T> vi6 = Unsafe.As<T, Vector<T>>(ref ri6);
|
||||
Vector<T> ve6 = Vector.Equals(vi6, vc);
|
||||
ref T ri6 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 6));
|
||||
Vector<T> vi6 = Unsafe.As<T, Vector<T>>(ref ri6);
|
||||
Vector<T> ve6 = Vector.Equals(vi6, vc);
|
||||
|
||||
partials -= ve6;
|
||||
partials -= ve6;
|
||||
|
||||
ref T ri7 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 7));
|
||||
Vector<T> vi7 = Unsafe.As<T, Vector<T>>(ref ri7);
|
||||
Vector<T> ve7 = Vector.Equals(vi7, vc);
|
||||
ref T ri7 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 7));
|
||||
Vector<T> vi7 = Unsafe.As<T, Vector<T>>(ref ri7);
|
||||
Vector<T> ve7 = Vector.Equals(vi7, vc);
|
||||
|
||||
partials -= ve7;
|
||||
partials -= ve7;
|
||||
|
||||
chunkLength -= Vector<T>.Count * 8;
|
||||
offset += Vector<T>.Count * 8;
|
||||
}
|
||||
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.
|
||||
Vector<T> vi = Unsafe.As<T, Vector<T>>(ref ri);
|
||||
Vector<T> 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);
|
||||
|
||||
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.
|
||||
Vector<T> vi = Unsafe.As<T, Vector<T>>(ref ri);
|
||||
Vector<T> 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
while (length >= Vector<T>.Count);
|
||||
}
|
||||
|
||||
/// <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
|
||||
// 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)
|
||||
{
|
||||
if (typeof(T) == typeof(sbyte))
|
||||
{
|
||||
return sbyte.MaxValue;
|
||||
}
|
||||
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();
|
||||
|
||||
if (typeof(T) == typeof(short))
|
||||
{
|
||||
return short.MaxValue;
|
||||
}
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(int))
|
||||
// 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 (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!;
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <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!;
|
||||
}
|
||||
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!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,310 +6,309 @@ using System.Diagnostics.Contracts;
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals
|
||||
namespace CommunityToolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to process sequences of values by reference.
|
||||
/// Calculates the djb2 hash for the target sequence of items of a given type.
|
||||
/// </summary>
|
||||
internal static partial class SpanHelper
|
||||
/// <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
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
int hash = 5381;
|
||||
nint offset = 0;
|
||||
// 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());
|
||||
|
||||
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;
|
||||
length -= 8;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
if (length >= 4)
|
||||
{
|
||||
int hash = 5381;
|
||||
nint offset = 0;
|
||||
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());
|
||||
|
||||
// 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))
|
||||
{
|
||||
Vector<int> vh = new(5381);
|
||||
Vector<int> v33 = new(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));
|
||||
Vector<int> vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
Vector<int> vp0 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp0, vi0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 1));
|
||||
Vector<int> vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
|
||||
Vector<int> vp1 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp1, vi1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 2));
|
||||
Vector<int> vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
|
||||
Vector<int> vp2 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp2, vi2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 3));
|
||||
Vector<int> vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
|
||||
Vector<int> vp3 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp3, vi3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 4));
|
||||
Vector<int> vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
|
||||
Vector<int> vp4 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp4, vi4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 5));
|
||||
Vector<int> vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
|
||||
Vector<int> vp5 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp5, vi5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 6));
|
||||
Vector<int> vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
|
||||
Vector<int> vp6 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp6, vi6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 7));
|
||||
Vector<int> vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
|
||||
Vector<int> 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);
|
||||
Vector<int> vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
Vector<int> 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 (int 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
ushort value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 1));
|
||||
ushort value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 2));
|
||||
ushort value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 3));
|
||||
ushort value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 4));
|
||||
ushort value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 5));
|
||||
ushort value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 6));
|
||||
ushort value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 7));
|
||||
ushort 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;
|
||||
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))
|
||||
{
|
||||
Vector<int> vh = new(5381);
|
||||
Vector<int> v33 = new(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));
|
||||
Vector<int> vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
|
||||
Vector<int> vp0 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp0, vi0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 1));
|
||||
Vector<int> vi1 = Unsafe.ReadUnaligned<Vector<int>>(ref ri1);
|
||||
Vector<int> vp1 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp1, vi1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 2));
|
||||
Vector<int> vi2 = Unsafe.ReadUnaligned<Vector<int>>(ref ri2);
|
||||
Vector<int> vp2 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp2, vi2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 3));
|
||||
Vector<int> vi3 = Unsafe.ReadUnaligned<Vector<int>>(ref ri3);
|
||||
Vector<int> vp3 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp3, vi3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 4));
|
||||
Vector<int> vi4 = Unsafe.ReadUnaligned<Vector<int>>(ref ri4);
|
||||
Vector<int> vp4 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp4, vi4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 5));
|
||||
Vector<int> vi5 = Unsafe.ReadUnaligned<Vector<int>>(ref ri5);
|
||||
Vector<int> vp5 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp5, vi5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 6));
|
||||
Vector<int> vi6 = Unsafe.ReadUnaligned<Vector<int>>(ref ri6);
|
||||
Vector<int> vp6 = Vector.Multiply(vh, v33);
|
||||
vh = Vector.Xor(vp6, vi6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 7));
|
||||
Vector<int> vi7 = Unsafe.ReadUnaligned<Vector<int>>(ref ri7);
|
||||
Vector<int> 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);
|
||||
Vector<int> vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
|
||||
Vector<int> 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 (int 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
ulong 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
uint 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));
|
||||
ushort value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value0);
|
||||
|
||||
ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 1));
|
||||
ushort value1 = Unsafe.ReadUnaligned<ushort>(ref ri1);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value1);
|
||||
|
||||
ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 2));
|
||||
ushort value2 = Unsafe.ReadUnaligned<ushort>(ref ri2);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value2);
|
||||
|
||||
ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 3));
|
||||
ushort value3 = Unsafe.ReadUnaligned<ushort>(ref ri3);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value3);
|
||||
|
||||
ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 4));
|
||||
ushort value4 = Unsafe.ReadUnaligned<ushort>(ref ri4);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value4);
|
||||
|
||||
ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 5));
|
||||
ushort value5 = Unsafe.ReadUnaligned<ushort>(ref ri5);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value5);
|
||||
|
||||
ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 6));
|
||||
ushort value6 = Unsafe.ReadUnaligned<ushort>(ref ri6);
|
||||
hash = unchecked(((hash << 5) + hash) ^ value6);
|
||||
|
||||
ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 7));
|
||||
ushort 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,142 +7,141 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with <see cref="object"/> instances.
|
||||
/// </summary>
|
||||
public static class ObjectMarshal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers for working with <see cref="object"/> instances.
|
||||
/// Calculates the byte offset to a specific field within a given <see cref="object"/>.
|
||||
/// </summary>
|
||||
public static class ObjectMarshal
|
||||
/// <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)
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
RawObjectData? rawObj = Unsafe.As<RawObjectData>(obj)!;
|
||||
ref byte r0 = ref rawObj.Data;
|
||||
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
|
||||
RawObjectData? 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)
|
||||
{
|
||||
RawObjectData? 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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
RawObjectData? 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ using System;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <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.
|
||||
|
@ -88,162 +88,161 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#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);
|
||||
|
||||
ActionInvoker<TAction> actionInvoker = new(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>
|
||||
/// 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>
|
||||
/// A contract for actions being executed with an input index.
|
||||
/// Executes a specified action in an optimized parallel loop.
|
||||
/// </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
|
||||
/// <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
|
||||
{
|
||||
/// <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);
|
||||
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);
|
||||
|
||||
ActionInvoker<TAction> actionInvoker = new(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);
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ using System.Drawing;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <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.
|
||||
|
@ -96,252 +96,251 @@ namespace CommunityToolkit.HighPerformance.Helpers
|
|||
}
|
||||
#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);
|
||||
|
||||
Action2DInvoker<TAction> actionInvoker = new(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>
|
||||
/// 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>
|
||||
/// A contract for actions being executed with two input indices.
|
||||
/// Executes a specified action in an optimized parallel loop.
|
||||
/// </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
|
||||
/// <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
|
||||
{
|
||||
/// <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);
|
||||
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);
|
||||
|
||||
Action2DInvoker<TAction> actionInvoker = new(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);
|
||||
}
|
||||
|
|
|
@ -7,166 +7,165 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
/// <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>
|
||||
{
|
||||
/// <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 (TItem? item in memory.Span)
|
||||
{
|
||||
Unsafe.AsRef(action).Invoke(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int batchSize = 1 + ((memory.Length - 1) / numBatches);
|
||||
|
||||
InActionInvoker<TItem, TAction> actionInvoker = new(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A contract for actions being executed on items of a specific type, with readonly access.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </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>
|
||||
/// <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>
|
||||
{
|
||||
/// <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);
|
||||
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 (TItem? item in memory.Span)
|
||||
{
|
||||
Unsafe.AsRef(action).Invoke(item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int batchSize = 1 + ((memory.Length - 1) / numBatches);
|
||||
|
||||
InActionInvoker<TItem, TAction> actionInvoker = new(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);
|
||||
}
|
||||
|
|
|
@ -6,155 +6,154 @@ using System;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
/// <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>
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
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);
|
||||
|
||||
InActionInvokerWithReadOnlyMemory2D<TItem, TAction> actionInvoker = new(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>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </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>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
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 lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
InActionInvokerWithReadOnlyMemory2D<TItem, TAction> actionInvoker = new(batchHeight, memory, action);
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
|
||||
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++)
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
Unsafe.AsRef(this.action).Invoke(in rStart);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(in rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,166 +7,165 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
/// <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>
|
||||
{
|
||||
/// <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 TItem item in memory.Span)
|
||||
{
|
||||
Unsafe.AsRef(action).Invoke(ref item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int batchSize = 1 + ((memory.Length - 1) / numBatches);
|
||||
|
||||
RefActionInvoker<TItem, TAction> actionInvoker = new(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(memory, default(TAction), 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A contract for actions being executed on items of a specific type, with side effect.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </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>
|
||||
/// <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>
|
||||
{
|
||||
/// <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);
|
||||
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 TItem item in memory.Span)
|
||||
{
|
||||
Unsafe.AsRef(action).Invoke(ref item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int batchSize = 1 + ((memory.Length - 1) / numBatches);
|
||||
|
||||
RefActionInvoker<TItem, TAction> actionInvoker = new(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);
|
||||
}
|
||||
|
|
|
@ -6,162 +6,161 @@ using System;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
/// <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>
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
ForEach(memory, default(TAction), 1);
|
||||
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);
|
||||
|
||||
RefActionInvokerWithReadOnlyMemory2D<TItem, TAction> actionInvoker = new(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>
|
||||
/// Executes a specified action in an optimized parallel loop over the input data.
|
||||
/// Processes the batch of actions at a specified index
|
||||
/// </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>
|
||||
/// <param name="i">The index of the batch to process</param>
|
||||
public void Invoke(int i)
|
||||
{
|
||||
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 lowY = i * this.batchHeight;
|
||||
nint highY = lowY + this.batchHeight;
|
||||
int
|
||||
cores = Environment.ProcessorCount,
|
||||
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
|
||||
batchHeight = 1 + ((memory.Height - 1) / numBatches);
|
||||
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
|
||||
width = this.memory.Width;
|
||||
|
||||
RefActionInvokerWithReadOnlyMemory2D<TItem, TAction> actionInvoker = new(batchHeight, memory, action);
|
||||
ReadOnlySpan2D<TItem> span = this.memory.Span;
|
||||
|
||||
// Skip the parallel invocation when possible
|
||||
if (numBatches == 1)
|
||||
for (int y = lowY; y < stopY; y++)
|
||||
{
|
||||
actionInvoker.Invoke(0);
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
|
||||
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++)
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
ref TItem rStart = ref span.DangerousGetReferenceAt(y, 0);
|
||||
ref TItem rEnd = ref Unsafe.Add(ref rStart, width);
|
||||
Unsafe.AsRef(this.action).Invoke(ref rStart);
|
||||
|
||||
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
|
||||
{
|
||||
Unsafe.AsRef(this.action).Invoke(ref rStart);
|
||||
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
rStart = ref Unsafe.Add(ref rStart, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,57 +4,56 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Helpers
|
||||
namespace CommunityToolkit.HighPerformance.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers to work with parallel code in a highly optimized manner.
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid parameter is specified for the minimum actions per thread.
|
||||
/// </summary>
|
||||
public static partial class ParallelHelper
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
|
||||
{
|
||||
/// <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");
|
||||
}
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,66 +7,65 @@ using System.Diagnostics.Contracts;
|
|||
using System.Runtime.CompilerServices;
|
||||
using static System.Math;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Memory.Internals
|
||||
namespace CommunityToolkit.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>
|
||||
/// A helper to validate arithmetic operations for <see cref="Memory2D{T}"/> and <see cref="Span2D{T}"/>.
|
||||
/// Ensures that the input parameters will not exceed the maximum native int value when indexing.
|
||||
/// </summary>
|
||||
internal static class OverflowHelper
|
||||
/// <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)
|
||||
{
|
||||
/// <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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,127 +4,126 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace CommunityToolkit.HighPerformance.Memory.Internals
|
||||
namespace CommunityToolkit.HighPerformance.Memory.Internals;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class to throw exceptions for memory types.
|
||||
/// </summary>
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class to throw exceptions for memory types.
|
||||
/// Throws an <see cref="ArgumentException"/> when using the <see langword="void"/>* constructor with a managed type.
|
||||
/// </summary>
|
||||
internal static class ThrowHelper
|
||||
public static void ThrowArgumentExceptionForManagedType()
|
||||
{
|
||||
/// <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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче