Switch whole solution to file-scoped namespaces

This commit is contained in:
Sergio Pedri 2021-11-01 20:46:46 +01:00
Родитель 32f3f7ce3c
Коммит c176080d37
262 изменённых файлов: 37539 добавлений и 37766 удалений

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

@ -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 &lt;= <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 &lt; <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 &lt; <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 &lt; <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 &lt;= <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 &lt; <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 &lt; <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 &lt;= <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 &lt;= <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 &lt; <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 &lt; <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 &lt; <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 &lt;= <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 &lt; <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 &lt; <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 &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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"/>) &lt;= <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 &lt;= <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 &lt; <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 &lt;= <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 &lt; <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&lt;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&lt;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&lt;byte> buffer = SpanOwner&lt;byte>.Allocate(1024))
/// {
/// // Use the buffer here...
/// }
/// </code>
/// As soon as the code leaves the scope of that <see langword="using"/> block, the underlying buffer will automatically
/// be disposed. The APIs in <see cref="SpanOwner{T}"/> rely on this pattern for extra performance, eg. they don't perform
/// the additional checks that are done in <see cref="MemoryOwner{T}"/> to ensure that the buffer hasn't been disposed
/// before returning a <see cref="Memory{T}"/> or <see cref="Span{T}"/> instance from it.
/// As such, this type should always be used with a <see langword="using"/> block or expression.
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(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&lt;byte> buffer = SpanOwner&lt;byte>.Allocate(1024))
/// {
/// // Use the buffer here...
/// }
/// </code>
/// As soon as the code leaves the scope of that <see langword="using"/> block, the underlying buffer will automatically
/// be disposed. The APIs in <see cref="SpanOwner{T}"/> rely on this pattern for extra performance, eg. they don't perform
/// the additional checks that are done in <see cref="MemoryOwner{T}"/> to ensure that the buffer hasn't been disposed
/// before returning a <see cref="Memory{T}"/> or <see cref="Span{T}"/> instance from it.
/// As such, this type should always be used with a <see langword="using"/> block or expression.
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(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*)&copy;
}
/// <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*)&copy;
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*)&copy;
long
negativeFlag = (long)rangeFlag - 1,
mask = ~negativeFlag;
return mask;
}
return *(byte*)&copy;
}
}
/// <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*)&copy;
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*)&copy;
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&lt;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&lt;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&lt;string&gt; words = new[] { "Hello", ", ", "world", "!" };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this ReadOnlySpan<T> span)
{
return new(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="ReadOnlySpan{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// ReadOnlySpan&lt;char&gt; text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="ReadOnlySpan{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T separator)
where T : IEquatable<T>
{
return new(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&lt;string&gt; words = new[] { "Hello", ", ", "world", "!" };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this ReadOnlySpan<T> span)
{
return new(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="ReadOnlySpan{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// ReadOnlySpan&lt;char&gt; text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="ReadOnlySpan{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T separator)
where T : IEquatable<T>
{
return new(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&lt;int&gt; numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in numbers.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this Span<T> span)
{
return new(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="Span{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// Span&lt;char&gt; text = "Hello, world!".ToCharArray();
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="Span{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
where T : IEquatable<T>
{
return new(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&lt;int&gt; numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in numbers.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this Span<T> span)
{
return new(span);
}
/// <summary>
/// Tokenizes the values in the input <see cref="Span{T}"/> instance using a specified separator.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// Span&lt;char&gt; text = "Hello, world!".ToCharArray();
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items in the <see cref="Span{T}"/> to tokenize.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to tokenize.</param>
/// <param name="separator">The separator <typeparamref name="T"/> item to use.</param>
/// <returns>A wrapper type that will handle the tokenization for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
where T : IEquatable<T>
{
return new(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(&amp;spinLock))
/// {
/// // Thread-safe code here...
/// }
/// </code>
/// The compiler will take care of releasing the SpinLock when the code goes out of that <see langword="using"/> scope.
/// </summary>
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(&amp;spinLock))
/// {
/// // Thread-safe code here...
/// }
/// </code>
/// The compiler will take care of releasing the SpinLock when the code goes out of that <see langword="using"/> scope.
/// 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*)&copy,
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*)&copy,
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*)&copy,
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*)&copy,
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&lt;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&lt;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");
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше