Merge branch 'master' into feature/win2dparser

This commit is contained in:
Ratish Philip 2020-12-16 11:22:25 -08:00 коммит произвёл GitHub
Родитель 609e249f81 2c0a0311c3
Коммит 7f8162ac92
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
487 изменённых файлов: 32400 добавлений и 5417 удалений

3
.github/PULL_REQUEST_TEMPLATE.md поставляемый
Просмотреть файл

@ -1,5 +1,8 @@
<!-- 🚨 Please Do Not skip any instructions and information mentioned below as they are all required and essential to evaluate and test the PR. By fulfilling all the required information you will be able to reduce the volume of questions and most likely help merge the PR faster 🚨 -->
<!-- 📝 It is preferred if you keep the "☑️ Allow edits by maintainers" checked in the Pull Request Template as it increases collaboration with the Toolkit maintainers by permitting commits to your PR branch (only) created from your fork. This can let us quickly make fixes for minor typos or forgotten StyleCop issues during review without needing to wait on you doing extra work. Let us help you help us! 🎉 -->
## Fixes #
<!-- Add the relevant issue number after the "#" mentioned above (for ex: Fixes #1234) which will automatically close the issue once the PR is merged. -->

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

@ -14,8 +14,9 @@
<IsTestProject>$(MSBuildProjectName.Contains('Test'))</IsTestProject>
<IsUwpProject Condition="'$(IsDesignProject)' != 'true'">$(MSBuildProjectName.Contains('Uwp'))</IsUwpProject>
<IsSampleProject>$(MSBuildProjectName.Contains('Sample'))</IsSampleProject>
<DefaultTargetPlatformVersion>19041</DefaultTargetPlatformVersion>
<DefaultTargetPlatformMinVersion>17763</DefaultTargetPlatformMinVersion>
<TargetPlatformBaseVersion>10.0</TargetPlatformBaseVersion>
<TargetPlatformRevision>19041</TargetPlatformRevision>
<TargetPlatformMinRevision>17763</TargetPlatformMinRevision>
<PackageOutputPath>$(MSBuildThisFileDirectory)bin\nupkg</PackageOutputPath>
</PropertyGroup>
@ -88,7 +89,6 @@
</Choose>
<PropertyGroup Condition="'$(IsUwpProject)' == 'true'">
<!-- 8002 is a strong named -> non-strong-named reference -->
<!-- This is valid for platforms other than .NET Framework (and is needed for the UWP targets -->
<NoWarn>$(NoWarn);8002</NoWarn>
@ -103,4 +103,7 @@
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)build\Windows.Toolkit.VisualStudio.Design.props" Condition="'$(IsDesignProject)' == 'true'"/>
</Project>

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

@ -1,26 +1,11 @@
<Project>
<Choose>
<When Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.17763' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">
<!-- UAP versions for uap10.0 where TPMV isn't implied -->
<PropertyGroup>
<TargetPlatformVersion>10.0.$(DefaultTargetPlatformVersion).0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.$(DefaultTargetPlatformMinVersion).0</TargetPlatformMinVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(DebugType)' == ''">
<DebugType>Portable</DebugType>
</PropertyGroup>
<PropertyGroup>
<UseUWP Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.17763' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">true</UseUWP>
</PropertyGroup>
<ItemGroup>
<SDKReference Condition="'$(UseWindowsDesktopSdk)' == 'true' " Include="WindowsDesktop, Version=$(TargetPlatformVersion)">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
</ItemGroup>
</When>
</Choose>
<Choose>
<When Condition="'$(TargetFramework.Contains(`uap10.0`))' == 'false' and '$(TargetFramework)' != 'native' and '$(IsSampleProject)' != 'true'">
<When Condition="!$(TargetFramework.Contains(`uap10.0`)) and '$(TargetFramework)' != 'native' and '$(IsSampleProject)' != 'true'">
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)toolkit.snk</AssemblyOriginatorKeyFile>
@ -28,6 +13,8 @@
</When>
</Choose>
<Import Project="$(MSBuildThisFileDirectory)build\Windows.Toolkit.Uwp.Build.targets" Condition="'$(UseUWP)' == 'true'"/>
<Target Name="AddCommitHashToAssemblyAttributes" BeforeTargets="GetAssemblyAttributes">
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition=" '$(SourceRevisionId)' != '' ">
@ -36,4 +23,5 @@
</AssemblyAttribute>
</ItemGroup>
</Target>
</Project>

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

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if !NET5_0
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Used to indicate to the compiler that the <c>.locals init</c> flag should not be set in method headers.
/// </summary>
/// <remarks>Internal copy of the .NET 5 attribute.</remarks>
[AttributeUsage(
AttributeTargets.Module |
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Interface |
AttributeTargets.Constructor |
AttributeTargets.Method |
AttributeTargets.Property |
AttributeTargets.Event,
Inherited = false)]
internal sealed class SkipLocalsInitAttribute : Attribute
{
}
}
#endif

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

@ -78,7 +78,7 @@ namespace Microsoft.Toolkit.HighPerformance
ThrowInvalidCastExceptionForGetFrom();
}
return Unsafe.As<Box<T>>(obj);
return Unsafe.As<Box<T>>(obj)!;
}
/// <summary>
@ -94,7 +94,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box<T> DangerousGetFrom(object obj)
{
return Unsafe.As<Box<T>>(obj);
return Unsafe.As<Box<T>>(obj)!;
}
/// <summary>
@ -108,7 +108,7 @@ namespace Microsoft.Toolkit.HighPerformance
{
if (obj.GetType() == typeof(T))
{
box = Unsafe.As<Box<T>>(obj);
box = Unsafe.As<Box<T>>(obj)!;
return true;
}
@ -145,7 +145,7 @@ namespace Microsoft.Toolkit.HighPerformance
// 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);
return Unsafe.As<Box<T>>(value)!;
}
/// <inheritdoc/>

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

@ -10,6 +10,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
@ -24,7 +25,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// the arrays in use are rented from the shared <see cref="ArrayPool{T}"/> instance,
/// and that <see cref="ArrayPoolBufferWriter{T}"/> is also available on .NET Standard 2.0.
/// </remarks>
[DebuggerTypeProxy(typeof(ArrayPoolBufferWriterDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class ArrayPoolBufferWriter<T> : IBuffer<T>, IMemoryOwner<T>
{
@ -233,7 +234,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Memory<T> GetMemory(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
CheckBufferAndEnsureCapacity(sizeHint);
return this.array.AsMemory(this.index);
}
@ -241,7 +242,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <inheritdoc/>
public Span<T> GetSpan(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
CheckBufferAndEnsureCapacity(sizeHint);
return this.array.AsSpan(this.index);
}
@ -251,9 +252,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// </summary>
/// <param name="sizeHint">The minimum number of items to ensure space for in <see cref="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckAndResizeBuffer(int sizeHint)
private void CheckBufferAndEnsureCapacity(int sizeHint)
{
if (this.array is null)
T[]? array = this.array;
if (array is null)
{
ThrowObjectDisposedException();
}
@ -268,14 +271,34 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
sizeHint = 1;
}
if (sizeHint > FreeCapacity)
if (sizeHint > array!.Length - this.index)
{
int minimumSize = this.index + sizeHint;
this.pool.Resize(ref this.array, minimumSize);
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()
{

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

@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using Microsoft.Toolkit.HighPerformance.Extensions;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <typeparamref name="TFrom"/> array, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ArrayMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <typeparamref name="TFrom"/> array to read data from.
/// </summary>
private readonly TFrom[] array;
/// <summary>
/// The starting offset within <see name="array"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="array"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="array">The source <typeparamref name="TFrom"/> array to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="array"/>.</param>
/// <param name="length">The original used length for <paramref name="array"/>.</param>
public ArrayMemoryManager(TFrom[] array, int offset, int length)
{
this.array = array;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
#if SPAN_RUNTIME_SUPPORT
ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset);
ref TTo r1 = ref Unsafe.As<TFrom, TTo>(ref r0);
int length = RuntimeHelpers.ConvertLength<TFrom, TTo>(this.length);
return MemoryMarshal.CreateSpan(ref r1, length);
#else
Span<TFrom> span = this.array.AsSpan(this.offset, this.length);
// We rely on MemoryMarshal.Cast here to deal with calculating the effective
// size of the new span to return. This will also make the behavior consistent
// for users that are both using this type as well as casting spans directly.
return MemoryMarshal.Cast<TFrom, TTo>(span);
#endif
}
/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);
ref TFrom r0 = ref this.array.DangerousGetReference();
ref byte r1 = ref Unsafe.As<TFrom, byte>(ref r0);
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
void* pi = Unsafe.AsPointer(ref r2);
return new MemoryHandle(pi, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// We need to calculate the right offset and length of the new Memory<T>. The local offset
// is the original offset into the wrapped TFrom[] array, while the input offset is the one
// with respect to TTo items in the Memory<TTo> instance that is currently being cast.
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
// We have a special handling in cases where the user is circling back to the original type
// of the wrapped array. In this case we can just return a memory wrapping that array directly,
// with offset and length being adjusted, without the memory manager indirection.
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.array.AsMemory(absoluteOffset, absoluteLength);
}
return new ArrayMemoryManager<TFrom, T>(this.array, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
}
}

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

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces
{
/// <summary>
/// An interface for a <see cref="MemoryManager{T}"/> instance that can reinterpret its underlying data.
/// </summary>
internal interface IMemoryManager
{
/// <summary>
/// Creates a new <see cref="Memory{T}"/> that reinterprets the underlying data for the current instance.
/// </summary>
/// <typeparam name="T">The target type to cast the items to.</typeparam>
/// <param name="offset">The starting offset within the data store.</param>
/// <param name="length">The original used length for the data store.</param>
/// <returns>A new <see cref="Memory{T}"/> instance of the specified type, reinterpreting the current items.</returns>
Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged;
}
}

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

@ -0,0 +1,134 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="MemoryManager{T}"/> of <typeparamref name="TFrom"/>, to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TFrom">The source type of items to read.</typeparam>
/// <typeparam name="TTo">The target type to cast the source items to.</typeparam>
internal sealed class ProxyMemoryManager<TFrom, TTo> : MemoryManager<TTo>, IMemoryManager
where TFrom : unmanaged
where TTo : unmanaged
{
/// <summary>
/// The source <see cref="MemoryManager{T}"/> to read data from.
/// </summary>
private readonly MemoryManager<TFrom> memoryManager;
/// <summary>
/// The starting offset within <see name="memoryManager"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="memoryManager"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="ProxyMemoryManager{TFrom, TTo}"/> class.
/// </summary>
/// <param name="memoryManager">The source <see cref="MemoryManager{T}"/> to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="memoryManager"/>.</param>
/// <param name="length">The original used length for <paramref name="memoryManager"/>.</param>
public ProxyMemoryManager(MemoryManager<TFrom> memoryManager, int offset, int length)
{
this.memoryManager = memoryManager;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
Span<TFrom> span = this.memoryManager.GetSpan().Slice(this.offset, this.length);
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<TFrom>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
#if NETSTANDARD1_4
int
shiftedOffset = byteOffset / Unsafe.SizeOf<TFrom>(),
remainder = byteOffset - (shiftedOffset * Unsafe.SizeOf<TFrom>());
#else
int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf<TFrom>(), out int remainder);
#endif
if (remainder != 0)
{
ThrowArgumentExceptionForInvalidAlignment();
}
return this.memoryManager.Pin(shiftedOffset);
}
/// <inheritdoc/>
public override void Unpin()
{
this.memoryManager.Unpin();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
((IDisposable)this.memoryManager).Dispose();
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
// Like in the other memory manager, calculate the absolute offset and length
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, TFrom>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, TFrom>(length);
// Skip one indirection level and slice the original memory manager, if possible
if (typeof(T) == typeof(TFrom))
{
return (Memory<T>)(object)this.memoryManager.Memory.Slice(absoluteOffset, absoluteLength);
}
return new ProxyMemoryManager<TFrom, T>(this.memoryManager, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when <see cref="Pin"/> receives an invalid target index.
/// </summary>
private static void ThrowArgumentExceptionForInvalidAlignment()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index doesn't result in an aligned item access");
}
}
}

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

@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if SPAN_RUNTIME_SUPPORT
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap arbitrary <see cref="object"/> instances.
/// </summary>
/// <typeparam name="T">The type of elements in the target memory area.</typeparam>
internal sealed class RawObjectMemoryManager<T> : MemoryManager<T>
{
/// <summary>
/// The target <see cref="object"/> instance.
/// </summary>
private readonly object instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The length of the target memory area.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="RawObjectMemoryManager{T}"/> class.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The starting offset within <paramref name="instance"/>.</param>
/// <param name="length">The usable length within <paramref name="instance"/>.</param>
public RawObjectMemoryManager(object instance, IntPtr offset, int length)
{
this.instance = instance;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<T> GetSpan()
{
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return MemoryMarshal.CreateSpan(ref r0, this.length);
}
/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)this.length)
{
ThrowArgumentOutOfRangeExceptionForInvalidElementIndex();
}
// Allocating a pinned handle for the array with fail and throw an exception
// if the array contains non blittable data. This is the expected behavior and
// the same happens when trying to pin a Memory<T> instance obtained through
// traditional means (eg. via the implicit T[] array conversion), if T is a
// reference type or a type containing some references.
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)elementIndex);
void* p = Unsafe.AsPointer(ref r1);
return new MemoryHandle(p, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input index for <see cref="Pin"/> is not valid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidElementIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input element index was not in the valid range");
}
}
}
#endif

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

@ -0,0 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
using Microsoft.Toolkit.HighPerformance.Extensions;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Internals
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that casts data from a <see cref="string"/> to <typeparamref name="TTo"/> values.
/// </summary>
/// <typeparam name="TTo">The target type to cast the source characters to.</typeparam>
internal sealed class StringMemoryManager<TTo> : MemoryManager<TTo>, IMemoryManager
where TTo : unmanaged
{
/// <summary>
/// The source <see cref="string"/> to read data from.
/// </summary>
private readonly string text;
/// <summary>
/// The starting offset within <see name="array"/>.
/// </summary>
private readonly int offset;
/// <summary>
/// The original used length for <see name="array"/>.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="StringMemoryManager{T}"/> class.
/// </summary>
/// <param name="text">The source <see cref="string"/> to read data from.</param>
/// <param name="offset">The starting offset within <paramref name="text"/>.</param>
/// <param name="length">The original used length for <paramref name="text"/>.</param>
public StringMemoryManager(string text, int offset, int length)
{
this.text = text;
this.offset = offset;
this.length = length;
}
/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
#if SPAN_RUNTIME_SUPPORT
ref char r0 = ref this.text.DangerousGetReferenceAt(this.offset);
ref TTo r1 = ref Unsafe.As<char, TTo>(ref r0);
int length = RuntimeHelpers.ConvertLength<char, TTo>(this.length);
return MemoryMarshal.CreateSpan(ref r1, length);
#else
ReadOnlyMemory<char> memory = this.text.AsMemory(this.offset, this.length);
Span<char> span = MemoryMarshal.AsMemory(memory).Span;
return MemoryMarshal.Cast<char, TTo>(span);
#endif
}
/// <inheritdoc/>
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf<char>() / Unsafe.SizeOf<TTo>()))
{
ThrowArgumentOutOfRangeExceptionForInvalidIndex();
}
int
bytePrefix = this.offset * Unsafe.SizeOf<char>(),
byteSuffix = elementIndex * Unsafe.SizeOf<TTo>(),
byteOffset = bytePrefix + byteSuffix;
GCHandle handle = GCHandle.Alloc(this.text, GCHandleType.Pinned);
ref char r0 = ref this.text.DangerousGetReference();
ref byte r1 = ref Unsafe.As<char, byte>(ref r0);
ref byte r2 = ref Unsafe.Add(ref r1, byteOffset);
void* pi = Unsafe.AsPointer(ref r2);
return new MemoryHandle(pi, handle);
}
/// <inheritdoc/>
public override void Unpin()
{
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public Memory<T> GetMemory<T>(int offset, int length)
where T : unmanaged
{
int
absoluteOffset = this.offset + RuntimeHelpers.ConvertLength<TTo, char>(offset),
absoluteLength = RuntimeHelpers.ConvertLength<TTo, char>(length);
if (typeof(T) == typeof(char))
{
ReadOnlyMemory<char> memory = this.text.AsMemory(absoluteOffset, absoluteLength);
return (Memory<T>)(object)MemoryMarshal.AsMemory(memory);
}
return new StringMemoryManager<T>(this.text, absoluteOffset, absoluteLength).Memory;
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the target index for <see cref="Pin"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex()
{
throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range");
}
}
}

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

@ -7,6 +7,7 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
namespace Microsoft.Toolkit.HighPerformance.Buffers
{
@ -20,7 +21,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// 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(MemoryBufferWriter<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryBufferWriter<T> : IBuffer<T>
{

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

@ -7,6 +7,9 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCORE_RUNTIME || NET5_0
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
@ -16,7 +19,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and a fast <see cref="Span{T}"/> accessor.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryOwnerDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public sealed class MemoryOwner<T> : IMemoryOwner<T>
{
@ -180,7 +183,22 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
ThrowObjectDisposedException();
}
#if NETCORE_RUNTIME || NET5_0
ref T r0 = ref array!.DangerousGetReferenceAt(this.start);
// On .NET Core runtimes, we can manually create a span from the starting reference to
// skip the argument validations, which include an explicit null check, covariance check
// for the array and the actual validation for the starting offset and target length. We
// only do this on .NET Core as we can leverage the runtime-specific array layout to get
// a fast access to the initial element, which makes this trick worth it. Otherwise, on
// runtimes where we would need to at least access a static field to retrieve the base
// byte offset within an SZ array object, we can get better performance by just using the
// default Span<T> constructor and paying the cost of the extra conditional branches,
// especially if T is a value type, in which case the covariance check is JIT removed.
return MemoryMarshal.CreateSpan(ref r0, this.length);
#else
return new Span<T>(array!, this.start, this.length);
#endif
}
}
@ -208,6 +226,31 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
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>
@ -222,7 +265,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// size and copy the previous items into the new one, or needing an additional variable/field
/// to manually handle to track the used range within a given <see cref="MemoryOwner{T}"/> instance.
/// </remarks>
[Pure]
public MemoryOwner<T> Slice(int start, int length)
{
T[]? array = this.array;
@ -244,6 +286,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
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!);
}

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

@ -7,6 +7,9 @@ using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCORE_RUNTIME || NET5_0
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Buffers.Views;
using Microsoft.Toolkit.HighPerformance.Extensions;
@ -31,7 +34,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// Not doing so will cause the underlying buffer not to be returned to the shared pool.
/// </summary>
/// <typeparam name="T">The type of items to store in the current instance.</typeparam>
[DebuggerTypeProxy(typeof(SpanOwnerDebugView<>))]
[DebuggerTypeProxy(typeof(MemoryDebugView<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly ref struct SpanOwner<T>
{
@ -143,7 +146,16 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Span<T>(this.array, 0, this.length);
get
{
#if NETCORE_RUNTIME || NET5_0
ref T r0 = ref array!.DangerousGetReference();
return MemoryMarshal.CreateSpan(ref r0, this.length);
#else
return new Span<T>(this.array, 0, this.length);
#endif
}
}
/// <summary>
@ -157,6 +169,23 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
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 ArraySegment<T>(array!, 0, this.length);
}
/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>

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

@ -1,19 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
#if NETCOREAPP3_1
using System.Numerics;
#endif
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Toolkit.HighPerformance.Extensions;
#if !NETSTANDARD1_4
using Microsoft.Toolkit.HighPerformance.Helpers;
#endif
using BitOperations = Microsoft.Toolkit.HighPerformance.Helpers.Internals.BitOperations;
#nullable enable
@ -79,8 +77,8 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
a = Math.Sqrt((double)size / factor),
b = factor * a;
x = RoundUpPowerOfTwo((int)a);
y = RoundUpPowerOfTwo((int)b);
x = BitOperations.RoundUpPowerOfTwo((int)a);
y = BitOperations.RoundUpPowerOfTwo((int)b);
}
// We want to find two powers of 2 factors that produce a number
@ -130,30 +128,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
Size = p2;
}
/// <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)]
private static int RoundUpPowerOfTwo(int x)
{
#if NETCOREAPP3_1
return 1 << (32 - BitOperations.LeadingZeroCount((uint)(x - 1)));
#else
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
#endif
}
/// <summary>
/// Gets the shared <see cref="StringPool"/> instance.
/// </summary>
@ -422,11 +396,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="value">The input <see cref="string"/> instance to cache.</param>
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Add(string value, int hashcode)
public void Add(string value, int hashcode)
{
ref string target = ref TryGet(value.AsSpan(), hashcode);
if (Unsafe.AreSame(ref target, ref Unsafe.AsRef<string>(null)))
if (Unsafe.IsNullRef(ref target))
{
Insert(value, hashcode);
}
@ -443,11 +417,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="value"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe string GetOrAdd(string value, int hashcode)
public string GetOrAdd(string value, int hashcode)
{
ref string result = ref TryGet(value.AsSpan(), hashcode);
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
if (!Unsafe.IsNullRef(ref result))
{
return result;
}
@ -464,11 +438,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="hashcode">The precomputed hashcode for <paramref name="span"/>.</param>
/// <returns>A <see cref="string"/> instance with the contents of <paramref name="span"/>, cached if possible.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
public string GetOrAdd(ReadOnlySpan<char> span, int hashcode)
{
ref string result = ref TryGet(span, hashcode);
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
if (!Unsafe.IsNullRef(ref result))
{
return result;
}
@ -488,11 +462,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="value">The resulting cached <see cref="string"/> instance, if present</param>
/// <returns>Whether or not the target <see cref="string"/> instance was found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
public bool TryGet(ReadOnlySpan<char> span, int hashcode, [NotNullWhen(true)] out string? value)
{
ref string result = ref TryGet(span, hashcode);
if (!Unsafe.AreSame(ref result, ref Unsafe.AsRef<string>(null)))
if (!Unsafe.IsNullRef(ref result))
{
value = result;
@ -527,7 +501,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
private unsafe ref string TryGet(ReadOnlySpan<char> span, int hashcode)
{
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
ref MapEntry entry = ref Unsafe.AsRef<MapEntry>(null);
ref MapEntry entry = ref Unsafe.NullRef<MapEntry>();
int
length = this.buckets.Length,
bucketIndex = hashcode & (length - 1);
@ -536,7 +510,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
(uint)i < (uint)length;
i = entry.NextIndex)
{
entry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)i);
entry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)i);
if (entry.HashCode == hashcode &&
entry.Value!.AsSpan().SequenceEqual(span))
@ -547,7 +521,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
}
return ref Unsafe.AsRef<string>(null);
return ref Unsafe.NullRef<string>();
}
/// <summary>
@ -556,7 +530,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="value">The new <see cref="string"/> instance to store.</param>
/// <param name="hashcode">The precomputed hashcode for <paramref name="value"/>.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void Insert(string value, int hashcode)
private void Insert(string value, int hashcode)
{
ref int bucketsRef = ref this.buckets.DangerousGetReference();
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
@ -571,7 +545,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
entryIndex = heapEntriesRef.MapIndex;
heapIndex = 0;
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
ref MapEntry removedEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
// The removal logic can be extremely optimized in this case, as we
// can retrieve the precomputed hashcode for the target entry by doing
@ -588,9 +562,9 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
int bucketIndex = hashcode & (this.buckets.Length - 1);
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (IntPtr)(void*)(uint)bucketIndex);
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)heapIndex);
ref int targetBucket = ref Unsafe.Add(ref bucketsRef, (nint)(uint)bucketIndex);
ref MapEntry targetMapEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
ref HeapEntry targetHeapEntry = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)heapIndex);
// Assign the values in the new map entry
targetMapEntry.HashCode = hashcode;
@ -616,7 +590,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <param name="mapIndex">The index of the target map node to remove.</param>
/// <remarks>The input <see cref="string"/> instance needs to already exist in the map.</remarks>
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void Remove(int hashcode, int mapIndex)
private void Remove(int hashcode, int mapIndex)
{
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
int
@ -628,7 +602,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// value we're looking for is guaranteed to be present
while (true)
{
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)entryIndex);
ref MapEntry candidate = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)entryIndex);
// Check the current value for a match
if (entryIndex == mapIndex)
@ -636,7 +610,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// If this was not the first list node, update the parent as well
if (lastIndex != EndOfList)
{
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)lastIndex);
ref MapEntry lastEntry = ref Unsafe.Add(ref mapEntriesRef, (nint)(uint)lastIndex);
lastEntry.NextIndex = candidate.NextIndex;
}
@ -662,14 +636,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// </summary>
/// <param name="heapIndex">The index of the target heap node to update.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void UpdateTimestamp(ref int heapIndex)
private void UpdateTimestamp(ref int heapIndex)
{
int
currentIndex = heapIndex,
count = this.count;
ref MapEntry mapEntriesRef = ref this.mapEntries.DangerousGetReference();
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
ref HeapEntry root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
uint timestamp = this.timestamp;
// Check if incrementing the current timestamp for the heap node to update
@ -721,7 +695,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// Check and update the left child, if necessary
if (left < count)
{
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)left);
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)left);
if (child.Timestamp < minimum.Timestamp)
{
@ -733,7 +707,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
// Same check as above for the right child
if (right < count)
{
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)right);
ref HeapEntry child = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)right);
if (child.Timestamp < minimum.Timestamp)
{
@ -752,8 +726,8 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
// Update the indices in the respective map entries (accounting for the swap)
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)root.MapIndex).HeapIndex = targetIndex;
Unsafe.Add(ref mapEntriesRef, (IntPtr)(void*)(uint)minimum.MapIndex).HeapIndex = currentIndex;
Unsafe.Add(ref mapEntriesRef, (nint)(uint)root.MapIndex).HeapIndex = targetIndex;
Unsafe.Add(ref mapEntriesRef, (nint)(uint)minimum.MapIndex).HeapIndex = currentIndex;
currentIndex = targetIndex;
@ -764,7 +738,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
minimum = temp;
// Update the reference to the root node
root = ref Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)currentIndex);
root = ref Unsafe.Add(ref heapEntriesRef, (nint)(uint)currentIndex);
}
Fallback:
@ -787,14 +761,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// a given number of nodes, those are all contiguous from the start of the array.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void UpdateAllTimestamps()
private void UpdateAllTimestamps()
{
int count = this.count;
ref HeapEntry heapEntriesRef = ref this.heapEntries.DangerousGetReference();
for (int i = 0; i < count; i++)
{
Unsafe.Add(ref heapEntriesRef, (IntPtr)(void*)(uint)i).Timestamp = (uint)i;
Unsafe.Add(ref heapEntriesRef, (nint)(uint)i).Timestamp = (uint)i;
}
}
}

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

@ -1,30 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <summary>
/// A debug proxy used to display items for the <see cref="ArrayPoolBufferWriter{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
internal sealed class ArrayPoolBufferWriterDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriterDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
public ArrayPoolBufferWriterDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
{
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

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

@ -1,30 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <summary>
/// A debug proxy used to display items for the <see cref="MemoryBufferWriter{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="ArrayPoolBufferWriter{T}"/> instances.</typeparam>
internal sealed class MemoryBufferWriterDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryBufferWriterDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
public MemoryBufferWriterDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
{
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

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

@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <summary>
/// A debug proxy used to display items in a 1D layout.
/// </summary>
/// <typeparam name="T">The type of items to display.</typeparam>
internal sealed class MemoryDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="arrayPoolBufferWriter">The input <see cref="ArrayPoolBufferWriter{T}"/> instance with the items to display.</param>
public MemoryDebugView(ArrayPoolBufferWriter<T>? arrayPoolBufferWriter)
{
this.Items = arrayPoolBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryBufferWriter">The input <see cref="MemoryBufferWriter{T}"/> instance with the items to display.</param>
public MemoryDebugView(MemoryBufferWriter<T>? memoryBufferWriter)
{
this.Items = memoryBufferWriter?.WrittenSpan.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
public MemoryDebugView(MemoryOwner<T>? memoryOwner)
{
this.Items = memoryOwner?.Span.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
public MemoryDebugView(SpanOwner<T> spanOwner)
{
this.Items = spanOwner.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public T[]? Items { get; }
}
}

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

@ -1,30 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <summary>
/// A debug proxy used to display items for the <see cref="MemoryOwner{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="MemoryOwner{T}"/> instances.</typeparam>
internal sealed class MemoryOwnerDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwnerDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memoryOwner">The input <see cref="MemoryOwner{T}"/> instance with the items to display.</param>
public MemoryOwnerDebugView(MemoryOwner<T>? memoryOwner)
{
this.Items = memoryOwner?.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

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

@ -1,30 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Buffers.Views
{
/// <summary>
/// A debug proxy used to display items for the <see cref="SpanOwner{T}"/> type.
/// </summary>
/// <typeparam name="T">The type of items stored in the input <see cref="SpanOwner{T}"/> instances.</typeparam>
internal sealed class SpanOwnerDebugView<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="SpanOwnerDebugView{T}"/> class with the specified parameters.
/// </summary>
/// <param name="spanOwner">The input <see cref="SpanOwner{T}"/> instance with the items to display.</param>
public SpanOwnerDebugView(SpanOwner<T> spanOwner)
{
this.Items = spanOwner.Span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[]? Items { get; }
}
}

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

@ -1,213 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates a column in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct Array2DColumnEnumerable<T>
{
/// <summary>
/// The source 2D <typeparamref name="T"/> array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target column to iterate within <see cref="array"/>.
/// </summary>
private readonly int column;
/// <summary>
/// Initializes a new instance of the <see cref="Array2DColumnEnumerable{T}"/> struct.
/// </summary>
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Array2DColumnEnumerable(T[,] array, int column)
{
this.array = array;
this.column = column;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.array, this.column);
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target column.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target column.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[Pure]
public T[] ToArray()
{
if ((uint)column >= (uint)this.array.GetLength(1))
{
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
}
int height = this.array.GetLength(0);
T[] array = new T[height];
ref T r0 = ref array.DangerousGetReference();
int i = 0;
// Leverage the enumerator to traverse the column
foreach (T item in this)
{
Unsafe.Add(ref r0, i++) = item;
}
return array;
}
/// <summary>
/// An enumerator for a source 2D array instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance mapping the target 2D array.
/// </summary>
/// <remarks>
/// In runtimes where we have support for the <see cref="Span{T}"/> type, we can
/// create one from the input 2D array and use that to traverse the target column.
/// This reduces the number of operations to perform for the offsetting to the right
/// column element (we simply need to add <see cref="width"/> to the offset at each
/// iteration to move down by one row), and allows us to use the fast <see cref="Span{T}"/>
/// accessor instead of the slower indexer for 2D arrays, as we can then access each
/// individual item linearly, since we know the absolute offset from the base location.
/// </remarks>
private readonly Span<T> span;
/// <summary>
/// The width of the target 2D array.
/// </summary>
private readonly int width;
/// <summary>
/// The current absolute offset within <see cref="span"/>.
/// </summary>
private int offset;
#else
/// <summary>
/// The source 2D array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target column to iterate within <see cref="array"/>.
/// </summary>
private readonly int column;
/// <summary>
/// The height of a column in <see cref="array"/>.
/// </summary>
private readonly int height;
/// <summary>
/// The current row.
/// </summary>
private int row;
#endif
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="array">The source 2D array instance.</param>
/// <param name="column">The target column to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(T[,] array, int column)
{
if ((uint)column >= (uint)array.GetLength(1))
{
ThrowArgumentOutOfRangeExceptionForInvalidColumn();
}
#if SPAN_RUNTIME_SUPPORT
this.span = array.AsSpan();
this.width = array.GetLength(1);
this.offset = column - this.width;
#else
this.array = array;
this.column = column;
this.height = array.GetLength(0);
this.row = -1;
#endif
}
/// <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()
{
#if SPAN_RUNTIME_SUPPORT
int offset = this.offset + this.width;
if ((uint)offset < (uint)this.span.Length)
{
this.offset = offset;
return true;
}
#else
int row = this.row + 1;
if (row < this.height)
{
this.row = row;
return true;
}
#endif
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
return ref this.span.DangerousGetReferenceAt(this.offset);
#else
return ref this.array[this.row, this.column];
#endif
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
{
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
}
}
}

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

@ -1,170 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if !SPAN_RUNTIME_SUPPORT
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct Array2DRowEnumerable<T>
{
/// <summary>
/// The source 2D <typeparamref name="T"/> array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target row to iterate within <see cref="array"/>.
/// </summary>
private readonly int row;
/// <summary>
/// Initializes a new instance of the <see cref="Array2DRowEnumerable{T}"/> struct.
/// </summary>
/// <param name="array">The source 2D <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Array2DRowEnumerable(T[,] array, int row)
{
this.array = array;
this.row = row;
}
/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current 2D <typeparamref name="T"/> array instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.array, this.row);
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target row.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[Pure]
public T[] ToArray()
{
if ((uint)row >= (uint)this.array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForInvalidRow();
}
int width = this.array.GetLength(1);
T[] array = new T[width];
for (int i = 0; i < width; i++)
{
array.DangerousGetReferenceAt(i) = this.array.DangerousGetReferenceAt(this.row, i);
}
return array;
}
/// <summary>
/// An enumerator for a source 2D array instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct Enumerator
{
/// <summary>
/// The source 2D array instance.
/// </summary>
private readonly T[,] array;
/// <summary>
/// The target row to iterate within <see cref="array"/>.
/// </summary>
private readonly int row;
/// <summary>
/// The width of a row in <see cref="array"/>.
/// </summary>
private readonly int width;
/// <summary>
/// The current column.
/// </summary>
private int column;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="array">The source 2D array instance.</param>
/// <param name="row">The target row to iterate within <paramref name="array"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(T[,] array, int row)
{
if ((uint)row >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForInvalidRow();
}
this.array = array;
this.row = row;
this.width = array.GetLength(1);
this.column = -1;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int column = this.column + 1;
if (column < this.width)
{
this.column = column;
return true;
}
return false;
}
/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
public ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
// This type is never used on .NET Core runtimes, where
// the fast indexer is available. Therefore, we can just
// use the built-in indexer for 2D arrays to access the value.
return ref this.array[this.row, this.column];
}
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
{
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
}
}
}
#endif

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

@ -0,0 +1,371 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if !SPAN_RUNTIME_SUPPORT
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates readonly items from arbitrary memory locations.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
public readonly ref struct ReadOnlyRefEnumerable<T>
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
private readonly ReadOnlySpan<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The total available length for the sequence.
/// </summary>
private readonly int length;
#endif
/// <summary>
/// The distance between items in the sequence to enumerate.
/// </summary>
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
private readonly int step;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyRefEnumerable(ReadOnlySpan<T> span, int step)
{
this.span = span;
this.step = step;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="reference">A reference to the first item of the sequence.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ReadOnlyRefEnumerable(in T reference, int length, int step)
{
this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length);
this.step = step;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step)
{
this.instance = instance;
this.offset = offset;
this.length = length;
this.step = step;
}
#endif
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator()
{
#if SPAN_RUNTIME_SUPPORT
return new Enumerator(this.span, this.step);
#else
return new Enumerator(this.instance, this.offset, this.length, this.step);
#endif
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyRefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(RefEnumerable<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
if (this.step == 1)
{
destination.CopyFrom(this.span);
return;
}
if (destination.Step == 1)
{
CopyTo(destination.Span);
return;
}
ref T sourceRef = ref this.span.DangerousGetReference();
ref T destinationRef = ref destination.Span.DangerousGetReference();
int
sourceLength = this.span.Length,
destinationLength = destination.Span.Length;
#else
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
int
sourceLength = this.length,
destinationLength = destination.Length;
#endif
if ((uint)destinationLength < (uint)sourceLength)
{
ThrowArgumentExceptionForDestinationTooShort();
}
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.step, (nint)(uint)destination.Step);
}
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyRefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(RefEnumerable<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
int
sourceLength = this.span.Length,
destinationLength = destination.Span.Length;
#else
int
sourceLength = this.length,
destinationLength = destination.Length;
#endif
if (destinationLength >= sourceLength)
{
CopyTo(destination);
return true;
}
return false;
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(Span<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
if (this.step == 1)
{
this.span.CopyTo(destination);
return;
}
ref T sourceRef = ref this.span.DangerousGetReference();
int length = this.span.Length;
#else
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
int length = this.length;
#endif
if ((uint)destination.Length < (uint)length)
{
ThrowArgumentExceptionForDestinationTooShort();
}
ref T destinationRef = ref destination.DangerousGetReference();
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.step);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Span<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
int length = this.span.Length;
#else
int length = this.length;
#endif
if (destination.Length >= length)
{
CopyTo(destination);
return true;
}
return false;
}
/// <inheritdoc cref="RefEnumerable{T}.ToArray"/>
[Pure]
public T[] ToArray()
{
#if SPAN_RUNTIME_SUPPORT
int length = this.span.Length;
#else
int length = this.length;
#endif
// Empty array if no data is mapped
if (length == 0)
{
return Array.Empty<T>();
}
T[] array = new T[length];
CopyTo(array);
return array;
}
/// <summary>
/// Implicitly converts a <see cref="RefEnumerable{T}"/> instance into a <see cref="ReadOnlyRefEnumerable{T}"/> one.
/// </summary>
/// <param name="enumerable">The input <see cref="RefEnumerable{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumerable)
{
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(enumerable.Span, enumerable.Step);
#else
return new ReadOnlyRefEnumerable<T>(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step);
#endif
}
/// <summary>
/// A custom enumerator type to traverse items within a <see cref="ReadOnlyRefEnumerable{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
private readonly ReadOnlySpan<T> span;
#else
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
private readonly object? instance;
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
private readonly IntPtr offset;
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
private readonly int length;
#endif
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
private readonly int step;
/// <summary>
/// The current position in the sequence.
/// </summary>
private int position;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The <see cref="ReadOnlySpan{T}"/> instance with the info on the items to traverse.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(ReadOnlySpan<T> span, int step)
{
this.span = span;
this.step = step;
this.position = -1;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(object? instance, IntPtr offset, int length, int step)
{
this.instance = instance;
this.offset = offset;
this.length = length;
this.step = step;
this.position = -1;
}
#endif
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
#if SPAN_RUNTIME_SUPPORT
return ++this.position < this.span.Length;
#else
return ++this.position < this.length;
#endif
}
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
public readonly ref readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref this.span.DangerousGetReference();
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
}
}
}
/// <summary>
/// Throws an <see cref="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");
}
}
}

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

@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int newIndex = this.index + 1;
if (newIndex < this.span.Length)
{
this.index = newIndex;
return true;
}
return false;
return ++this.index < this.span.Length;
}
/// <summary>
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
// See comment in SpanEnumerable<T> about this
return new Item(ref ri, this.index);
@ -139,7 +130,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
return ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
return ref ri;
#endif

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

@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// </summary>
/// <param name="span">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpanTokenizer(ReadOnlySpan<T> span, T separator)
{
this.span = span;

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

@ -0,0 +1,464 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if !SPAN_RUNTIME_SUPPORT
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that iterates items from arbitrary memory locations.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
public readonly ref struct RefEnumerable<T>
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>The <see cref="Span{T}.Length"/> field maps to the total available length.</remarks>
internal readonly Span<T> Span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
internal readonly object? Instance;
/// <summary>
/// The initial offset within <see cref="Instance"/>.
/// </summary>
internal readonly IntPtr Offset;
/// <summary>
/// The total available length for the sequence.
/// </summary>
internal 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>
internal readonly int Step;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
/// </summary>
/// <param name="reference">A reference to the first item of the sequence.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal RefEnumerable(ref T reference, int length, int step)
{
Span = MemoryMarshal.CreateSpan(ref reference, length);
Step = step;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
{
Instance = instance;
Offset = offset;
Length = length;
Step = step;
}
#endif
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator()
{
#if SPAN_RUNTIME_SUPPORT
return new Enumerator(this.Span, this.Step);
#else
return new Enumerator(this.Instance, this.Offset, this.Length, this.Step);
#endif
}
/// <summary>
/// Clears the contents of the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
public void Clear()
{
#if SPAN_RUNTIME_SUPPORT
// Fast path for contiguous items
if (this.Step == 1)
{
this.Span.Clear();
return;
}
ref T r0 = ref this.Span.DangerousGetReference();
int length = this.Span.Length;
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
int length = this.Length;
#endif
RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step);
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="RefEnumerable{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(RefEnumerable<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
if (this.Step == 1)
{
destination.CopyFrom(this.Span);
return;
}
if (destination.Step == 1)
{
CopyTo(destination.Span);
return;
}
ref T sourceRef = ref this.Span.DangerousGetReference();
ref T destinationRef = ref destination.Span.DangerousGetReference();
int
sourceLength = this.Span.Length,
destinationLength = destination.Span.Length;
#else
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
int
sourceLength = this.Length,
destinationLength = destination.Length;
#endif
if ((uint)destinationLength < (uint)sourceLength)
{
ThrowArgumentExceptionForDestinationTooShort();
}
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="RefEnumerable{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="RefEnumerable{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(RefEnumerable<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
int
sourceLength = this.Span.Length,
destinationLength = destination.Span.Length;
#else
int
sourceLength = this.Length,
destinationLength = destination.Length;
#endif
if (destinationLength >= sourceLength)
{
CopyTo(destination);
return true;
}
return false;
}
/// <summary>
/// Copies the contents of this <see cref="RefEnumerable{T}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefEnumerable{T}"/> instance.
/// </exception>
public void CopyTo(Span<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
if (this.Step == 1)
{
this.Span.CopyTo(destination);
return;
}
ref T sourceRef = ref this.Span.DangerousGetReference();
int length = this.Span.Length;
#else
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
int length = this.Length;
#endif
if ((uint)destination.Length < (uint)length)
{
ThrowArgumentExceptionForDestinationTooShort();
}
ref T destinationRef = ref destination.DangerousGetReference();
RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step);
}
/// <summary>
/// Attempts to copy the current <see cref="RefEnumerable{T}"/> instance to a destination <see cref="Span{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Span{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Span<T> destination)
{
#if SPAN_RUNTIME_SUPPORT
int length = this.Span.Length;
#else
int length = this.Length;
#endif
if (destination.Length >= length)
{
CopyTo(destination);
return true;
}
return false;
}
/// <summary>
/// Copies the contents of a source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when the current <see cref="RefEnumerable{T}"/> is shorter than the source <see cref="ReadOnlySpan{T}"/> instance.
/// </exception>
internal void CopyFrom(ReadOnlySpan<T> source)
{
#if SPAN_RUNTIME_SUPPORT
if (this.Step == 1)
{
source.CopyTo(this.Span);
return;
}
ref T destinationRef = ref this.Span.DangerousGetReference();
int destinationLength = this.Span.Length;
#else
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
int destinationLength = this.Length;
#endif
ref T sourceRef = ref source.DangerousGetReference();
int sourceLength = source.Length;
if ((uint)destinationLength < (uint)sourceLength)
{
ThrowArgumentExceptionForDestinationTooShort();
}
RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step);
}
/// <summary>
/// Attempts to copy the source <see cref="ReadOnlySpan{T}"/> into the current <see cref="RefEnumerable{T}"/> instance.
/// </summary>
/// <param name="source">The source <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyFrom(ReadOnlySpan<T> source)
{
#if SPAN_RUNTIME_SUPPORT
int length = this.Span.Length;
#else
int length = this.Length;
#endif
if (length >= source.Length)
{
CopyFrom(source);
return true;
}
return false;
}
/// <summary>
/// Fills the elements of this <see cref="RefEnumerable{T}"/> with a specified value.
/// </summary>
/// <param name="value">The value to assign to each element of the <see cref="RefEnumerable{T}"/> instance.</param>
public void Fill(T value)
{
#if SPAN_RUNTIME_SUPPORT
if (this.Step == 1)
{
this.Span.Fill(value);
return;
}
ref T r0 = ref this.Span.DangerousGetReference();
int length = this.Span.Length;
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.Instance, this.Offset);
int length = this.Length;
#endif
RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value);
}
/// <summary>
/// Returns a <typeparamref name="T"/> array with the values in the target row.
/// </summary>
/// <returns>A <typeparamref name="T"/> array with the values in the target row.</returns>
/// <remarks>
/// This method will allocate a new <typeparamref name="T"/> array, so only
/// use it if you really need to copy the target items in a new memory location.
/// </remarks>
[Pure]
public T[] ToArray()
{
#if SPAN_RUNTIME_SUPPORT
int length = this.Span.Length;
#else
int length = this.Length;
#endif
// Empty array if no data is mapped
if (length == 0)
{
return Array.Empty<T>();
}
T[] array = new T[length];
CopyTo(array);
return array;
}
/// <summary>
/// A custom enumerator type to traverse items within a <see cref="RefEnumerable{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <inheritdoc cref="RefEnumerable{T}.Span"/>
private readonly Span<T> span;
#else
/// <inheritdoc cref="RefEnumerable{T}.Instance"/>
private readonly object? instance;
/// <inheritdoc cref="RefEnumerable{T}.Offset"/>
private readonly IntPtr offset;
/// <inheritdoc cref="RefEnumerable{T}.Length"/>
private readonly int length;
#endif
/// <inheritdoc cref="RefEnumerable{T}.Step"/>
private readonly int step;
/// <summary>
/// The current position in the sequence.
/// </summary>
private int position;
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/> instance with the info on the items to traverse.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(Span<T> span, int step)
{
this.span = span;
this.step = step;
this.position = -1;
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see paramref="instance"/>.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(object? instance, IntPtr offset, int length, int step)
{
this.instance = instance;
this.offset = offset;
this.length = length;
this.step = step;
this.position = -1;
}
#endif
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
#if SPAN_RUNTIME_SUPPORT
return ++this.position < this.span.Length;
#else
return ++this.position < this.length;
#endif
}
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
public readonly ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref this.span.DangerousGetReference();
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
// Here we just offset by shifting down as if we were traversing a 2D array with a
// a single column, with the width of each row represented by the step, the height
// represented by the current position, and with only the first element of each row
// being inspected. We can perform all the indexing operations in this type as nint,
// as the maximum offset is guaranteed never to exceed the maximum value, since on
// 32 bit architectures it's not possible to allocate that much memory anyway.
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
}
}
}
/// <summary>
/// Throws an <see cref="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");
}
}
}

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

@ -54,16 +54,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int newIndex = this.index + 1;
if (newIndex < this.span.Length)
{
this.index = newIndex;
return true;
}
return false;
return ++this.index < this.span.Length;
}
/// <summary>
@ -76,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
// support for the Span<T> types), we can save 4 bytes by piggybacking the
@ -144,7 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
return ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref MemoryMarshal.GetReference(this.span);
ref T ri = ref Unsafe.Add(ref r0, this.index);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)this.index);
return ref ri;
#endif

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

@ -43,6 +43,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
/// <param name="separator">The separator item to use.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanTokenizer(Span<T> span, T separator)
{
this.span = span;

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

@ -5,11 +5,12 @@
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCORE_RUNTIME
#if NETCORE_RUNTIME || NET5_0
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@ -29,26 +30,17 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReference<T>(this T[] array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array);
#if NET5_0
return ref MemoryMarshal.GetArrayDataReference(array);
#elif NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array)!;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
#else
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
// Checking the length of the array like so allows the JIT
// to skip its own bounds check, which results in the element
// access below to be executed without branches.
if (0u < (uint)array.Length)
{
return ref array[0];
}
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
unsafe
{
return ref Unsafe.AsRef<T>(null);
}
#pragma warning restore SA1131
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
#endif
}
@ -62,21 +54,25 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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 unsafe ref T DangerousGetReferenceAt<T>(this T[] array, int i)
public static ref T DangerousGetReferenceAt<T>(this T[] array, int i)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array);
#if NET5_0
ref T r0 = ref MemoryMarshal.GetArrayDataReference(array);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
#elif NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array)!;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
#else
if ((uint)i < (uint)array.Length)
{
return ref array[i];
}
IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>();
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref Unsafe.AsRef<T>(null);
return ref ri;
#endif
}
@ -111,13 +107,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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)
public static int Count<T>(this T[] array, T value)
where T : IEquatable<T>
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)array.Length;
nint
length = RuntimeHelpers.GetArrayNativeLength(array),
count = SpanHelper.Count(ref r0, length, value);
return SpanHelper.Count(ref r0, length, value);
if ((nuint)count > int.MaxValue)
{
ThrowOverflowException();
}
return (int)count;
}
/// <summary>
@ -182,13 +185,34 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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)
public static int GetDjb2HashCode<T>(this T[] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)array.Length;
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();
}
}
}

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

@ -4,13 +4,15 @@
using System;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
using Microsoft.Toolkit.HighPerformance.Memory;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@ -31,22 +33,14 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static ref T DangerousGetReference<T>(this T[,] array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(array);
var arrayData = Unsafe.As<RawArray2DData>(array)!;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
#else
#pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check
if (0u < (uint)array.Length)
{
return ref array[0, 0];
}
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
unsafe
{
return ref Unsafe.AsRef<T>(null);
}
#pragma warning restore SA1131
return ref array.DangerousGetObjectDataReferenceAt<T>(offset);
#endif
}
@ -66,23 +60,23 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
public static ref T DangerousGetReferenceAt<T>(this T[,] array, int i, int j)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(array);
int offset = (i * arrayData.Width) + j;
var arrayData = Unsafe.As<RawArray2DData>(array)!;
nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)offset);
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
#else
if ((uint)i < (uint)array.GetLength(0) &&
(uint)j < (uint)array.GetLength(1))
{
return ref array[i, j];
}
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 array.DangerousGetObjectDataReferenceAt<T>(offset);
ref T ri = ref Unsafe.Add(ref r0, index);
return ref Unsafe.AsRef<T>(null);
return ref ri;
#endif
}
@ -112,95 +106,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
#endif
/// <summary>
/// Fills an area in a given 2D <typeparamref name="T"/> array instance with a specified value.
/// This API will try to fill as many items as possible, ignoring positions outside the bounds of the array.
/// If invalid coordinates are given, they will simply be ignored and no exception will be thrown.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="value">The <typeparamref name="T"/> value to fill the target area with.</param>
/// <param name="row">The row to start on (inclusive, 0-based index).</param>
/// <param name="column">The column to start on (inclusive, 0-based index).</param>
/// <param name="width">The positive width of area to fill.</param>
/// <param name="height">The positive height of area to fill.</param>
public static void Fill<T>(this T[,] array, T value, int row, int column, int width, int height)
{
Rectangle bounds = new Rectangle(0, 0, array.GetLength(1), array.GetLength(0));
// Precompute bounds to skip branching in main loop
bounds.Intersect(new Rectangle(column, row, width, height));
for (int i = bounds.Top; i < bounds.Bottom; i++)
{
#if SPAN_RUNTIME_SUPPORT
#if NETCORE_RUNTIME
ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left);
#else
ref T r0 = ref array[i, bounds.Left];
#endif
// Span<T>.Fill will use vectorized instructions when possible
MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value);
#else
ref T r0 = ref array[i, bounds.Left];
for (int j = 0; j < bounds.Width; j++)
{
// Storing the initial reference and only incrementing
// that one in each iteration saves one additional indirect
// dereference for every loop iteration compared to using
// the DangerousGetReferenceAt<T> extension on the array.
Unsafe.Add(ref r0, j) = value;
}
#endif
}
}
/// <summary>
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// 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="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <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
#if SPAN_RUNTIME_SUPPORT
Span<T>
#else
// .NET Standard 2.0 lacks MemoryMarshal.CreateSpan<T>(ref T, int),
// which is necessary to create arbitrary Span<T>-s over a 2D array.
// To work around this, we use a custom ref struct enumerator,
// which makes the lack of that API completely transparent to the user.
// If a user then moves from .NET Standard 2.0 to 2.1, all the previous
// features will be perfectly supported, and in addition to that it will
// also gain the ability to use the Span<T> value elsewhere.
// The only case where this would be a breaking change for a user upgrading
// the target framework is when the returned enumerator type is used directly,
// but since that's specifically discouraged from the docs, we don't
// need to worry about that scenario in particular, as users doing that
// would be willingly go against the recommended usage of this API.
Array2DRowEnumerable<T>
#endif
GetRow<T>(this T[,] array, int row)
public static RefEnumerable<T> GetRow<T>(this T[,] array, int row)
{
if ((uint)row >= (uint)array.GetLength(0))
if (array.IsCovariant())
{
throw new ArgumentOutOfRangeException(nameof(row));
ThrowArrayTypeMismatchException();
}
int height = array.GetLength(0);
if ((uint)row >= (uint)height)
{
ThrowArgumentOutOfRangeExceptionForRow();
}
int width = array.GetLength(1);
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
return new RefEnumerable<T>(ref r0, width, 1);
#else
return new Array2DRowEnumerable<T>(array, row);
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
return new RefEnumerable<T>(array, offset, width, 1);
#endif
}
/// <summary>
/// Returns an enumerable that returns the items from a given column in a given 2D <typeparamref name="T"/> array instance.
/// 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 =
@ -221,15 +166,196 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="column">The target column to retrieve (0-based index).</param>
/// <returns>A wrapper type that will handle the column enumeration for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="Array2DColumnEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <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 Array2DColumnEnumerable<T> GetColumn<T>(this T[,] array, int column)
public static RefEnumerable<T> GetColumn<T>(this T[,] array, int column)
{
return new Array2DColumnEnumerable<T>(array, column);
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
int width = array.GetLength(1);
if ((uint)column >= (uint)width)
{
ThrowArgumentOutOfRangeExceptionForColumn();
}
int height = array.GetLength(0);
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
return new RefEnumerable<T>(ref r0, height, width);
#else
ref T r0 = ref array.DangerousGetReferenceAt(0, column);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
return new RefEnumerable<T>(array, offset, height, width);
#endif
}
/// <summary>
/// Creates a new <see cref="Span2D{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Span2D{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,]? array)
{
return new Span2D<T>(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 Span2D<T>(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 Memory2D<T>(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 Memory2D<T>(array, row, column, height, width);
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="Span{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to retrieve (0-based index).</param>
/// <returns>A <see cref="Span{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this T[,] array, int row)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)row >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForRow();
}
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1));
}
/// <summary>
/// Returns a <see cref="Memory{T}"/> over a row in a given 2D <typeparamref name="T"/> array instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <param name="row">The target row to retrieve (0-based index).</param>
/// <returns>A <see cref="Memory{T}"/> with the items from the target row within <paramref name="array"/>.</returns>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="row"/> is invalid.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this T[,] array, int row)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)row >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForRow();
}
ref T r0 = ref array.DangerousGetReferenceAt(row, 0);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
return new RawObjectMemoryManager<T>(array, offset, array.GetLength(1)).Memory;
}
/// <summary>
/// Creates a new <see cref="Memory{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 2D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 2D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(this T[,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset<T>();
int length = array.Length;
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
}
/// <summary>
/// Creates a new <see cref="Span{T}"/> over an input 2D <typeparamref name="T"/> array.
/// </summary>
@ -238,26 +364,21 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[,] array)
public static Span<T> AsSpan<T>(this T[,]? array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray2DData>(array);
if (array is null)
{
return default;
}
// On x64, the length is padded to x64, but it is represented in memory
// as two sequential uint fields (one of which is padding).
// So we can just reinterpret a reference to the IntPtr as one of type
// uint, to access the first 4 bytes of that field, regardless of whether
// we're running in a 32 or 64 bit process. This will work when on little
// endian systems as well, as the memory layout for fields is the same,
// the only difference is the order of bytes within each field of a given type.
// We use checked here to follow suit with the CoreCLR source, where an
// invalid value here should fail to perform the cast and throw an exception.
int length = checked((int)Unsafe.As<IntPtr, uint>(ref arrayData.Length));
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
#else
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
ref T r0 = ref array.DangerousGetReference();
int length = array.Length;
ref T r0 = ref array[0, 0];
#endif
return MemoryMarshal.CreateSpan(ref r0, length);
}
#endif
@ -275,9 +396,16 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
where T : IEquatable<T>
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)array.Length;
nint
length = RuntimeHelpers.GetArrayNativeLength(array),
count = SpanHelper.Count(ref r0, length, value);
return SpanHelper.Count(ref r0, length, value);
if ((nuint)count > int.MaxValue)
{
ThrowOverflowException();
}
return (int)count;
}
/// <summary>
@ -294,9 +422,46 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)array.Length;
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");
}
}
}

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

@ -0,0 +1,324 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
using Microsoft.Toolkit.HighPerformance.Memory;
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="Array"/> type.
/// </summary>
public static partial class ArrayExtensions
{
/// <summary>
/// Returns a reference to the first element within a given 3D <typeparamref name="T"/> array, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input <typeparamref name="T"/> array instance.</param>
/// <returns>A reference to the first element within <paramref name="array"/>, or the location it would have used, if <paramref name="array"/> is empty.</returns>
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReference<T>(this T[,,] array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray3DData>(array)!;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
return ref r0;
#else
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
return ref array.DangerousGetObjectDataReferenceAt<T>(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 responsability of the caller to adjust the input
/// indices to account for the actual lower bounds, if the input array has either axis not starting at 0.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this T[,,] array, int i, int j, int k)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArray3DData>(array)!;
nint offset =
((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
#else
int
height = array.GetLength(1),
width = array.GetLength(2);
nint index =
((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
ref T r0 = ref array.DangerousGetObjectDataReferenceAt<T>(offset);
ref T ri = ref Unsafe.Add(ref r0, index);
return ref ri;
#endif
}
#if NETCORE_RUNTIME
// See description for this in the 2D partial file.
// Using the CHW naming scheme here (like with RGB images).
[StructLayout(LayoutKind.Sequential)]
private sealed class RawArray3DData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
public IntPtr Length;
public int Channel;
public int Height;
public int Width;
public int ChannelLowerBound;
public int HeightLowerBound;
public int WidthLowerBound;
public byte Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
#endif
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Creates a new <see cref="Memory{T}"/> over an input 3D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Memory{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(this T[,,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset<T>();
int length = array.Length;
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
}
/// <summary>
/// Creates a new <see cref="Span{T}"/> over an input 3D <typeparamref name="T"/> array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The input 3D <typeparamref name="T"/> array instance.</param>
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[,,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
ref T r0 = ref array.DangerousGetReference();
int length = array.Length;
return MemoryMarshal.CreateSpan(ref r0, length);
}
/// <summary>
/// Creates a new instance of the <see cref="Span{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Span{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForDepth();
}
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
int length = checked(array.GetLength(1) * array.GetLength(2));
return MemoryMarshal.CreateSpan(ref r0, length);
}
/// <summary>
/// Creates a new instance of the <see cref="Memory{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Memory{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> AsMemory<T>(this T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForDepth();
}
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
IntPtr offset = array.DangerousGetObjectDataByteOffset(ref r0);
int length = checked(array.GetLength(1) * array.GetLength(2));
return new RawObjectMemoryManager<T>(array, offset, length).Memory;
}
#endif
/// <summary>
/// Creates a new instance of the <see cref="Span2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <typeparam name="T">The type of elements in the input 3D <typeparamref name="T"/> array instance.</typeparam>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when either <paramref name="depth"/> is invalid.</exception>
/// <returns>A <see cref="Span2D{T}"/> instance wrapping the target layer within <paramref name="array"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth)
{
return new Span2D<T>(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 Memory2D<T>(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");
}
}
}

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

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Streams;
using Microsoft.Toolkit.HighPerformance.Streams.Sources;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="ArrayPoolBufferWriter{T}"/> type.
/// </summary>
public static class ArrayPoolBufferWriterExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="ArrayPoolBufferWriter{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="writer">The target <see cref="ArrayPoolBufferWriter{T}"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ArrayPoolBufferWriter<byte> writer)
{
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(writer));
}
}
}

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

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
@ -12,6 +13,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class BoolExtensions
{
/// <summary>
/// Converts the given <see cref="bool"/> value into a <see cref="byte"/>.
/// </summary>
/// <param name="flag">The input value to convert.</param>
/// <returns>1 if <paramref name="flag"/> is <see langword="true"/>, 0 otherwise.</returns>
/// <remarks>This method does not contain branching instructions.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe byte ToByte(this bool flag)
{
return *(byte*)&flag;
}
/// <summary>
/// Converts the given <see cref="bool"/> value into an <see cref="int"/>.
/// </summary>
@ -20,9 +34,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method does not contain branching instructions.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToInt(this bool flag)
[Obsolete("Use ToByte instead.")]
public static unsafe int ToInt(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);
return *(byte*)&flag;
}
/// <summary>
@ -41,9 +56,9 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToBitwiseMask32(this bool flag)
public static unsafe int ToBitwiseMask32(this bool flag)
{
byte rangeFlag = Unsafe.As<bool, byte>(ref flag);
byte rangeFlag = *(byte*)&flag;
int
negativeFlag = rangeFlag - 1,
mask = ~negativeFlag;
@ -60,9 +75,9 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method does not contain branching instructions. See additional note in <see cref="ToBitwiseMask32"/>.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ToBitwiseMask64(this bool flag)
public static unsafe long ToBitwiseMask64(this bool flag)
{
byte rangeFlag = Unsafe.As<bool, byte>(ref flag);
byte rangeFlag = *(byte*)&flag;
long
negativeFlag = (long)rangeFlag - 1,
mask = ~negativeFlag;

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

@ -23,12 +23,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add<T>(ref this HashCode hashCode, ReadOnlySpan<T> span)
#if SPAN_RUNTIME_SUPPORT
where T : notnull
#else
// Same type constraints as HashCode<T>, see comments there
where T : unmanaged
#endif
{
int hash = HashCode<T>.CombineValues(span);

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

@ -4,8 +4,13 @@
using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
using Microsoft.Toolkit.HighPerformance.Streams;
using Microsoft.Toolkit.HighPerformance.Streams.Sources;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@ -14,6 +19,28 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class IBufferWriterExtensions
{
/// <summary>
/// Returns a <see cref="Stream"/> that can be used to write to a target an <see cref="IBufferWriter{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="writer">The target <see cref="IBufferWriter{T}"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping <paramref name="writer"/> and writing data to its underlying buffer.</returns>
/// <remarks>The returned <see cref="Stream"/> can only be written to and does not support seeking.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this IBufferWriter<byte> writer)
{
if (writer.GetType() == typeof(ArrayPoolBufferWriter<byte>))
{
// If the input writer is of type ArrayPoolBufferWriter<byte>, we can use the type
// specific buffer writer owner to let the JIT elide callvirts when accessing it.
var internalWriter = Unsafe.As<ArrayPoolBufferWriter<byte>>(writer)!;
return new IBufferWriterStream<ArrayBufferWriterOwner>(new ArrayBufferWriterOwner(internalWriter));
}
return new IBufferWriterStream<IBufferWriterOwner>(new IBufferWriterOwner(writer));
}
/// <summary>
/// Writes a value of a specified type into a target <see cref="IBufferWriter{T}"/> instance.
/// </summary>

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

@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if NET5_0
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
/// <summary>
/// Helpers for working with the <see cref="List{T}"/> type.
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Creates a new <see cref="Span{T}"/> over an input <see cref="List{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="List{T}"/> instance.</typeparam>
/// <param name="list">The input <see cref="List{T}"/> instance.</param>
/// <returns>A <see cref="Span{T}"/> instance with the values of <paramref name="list"/>.</returns>
/// <remarks>
/// Note that the returned <see cref="Span{T}"/> is only guaranteed to be valid as long as the items within
/// <paramref name="list"/> are not modified. Doing so might cause the <see cref="List{T}"/> to swap its
/// internal buffer, causing the returned <see cref="Span{T}"/> to become out of date. That means that in this
/// scenario, the <see cref="Span{T}"/> would end up wrapping an array no longer in use. Always make sure to use
/// the returned <see cref="Span{T}"/> while the target <see cref="List{T}"/> is not modified.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this List<T>? list)
{
return CollectionsMarshal.AsSpan(list);
}
}
}
#endif

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

@ -6,6 +6,10 @@ using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@ -15,6 +19,87 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class MemoryExtensions
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width)
{
return new Memory2D<T>(memory, height, width);
}
/// <summary>
/// Returns a <see cref="Memory2D{T}"/> instance wrapping the underlying data for the given <see cref="Memory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Memory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="Memory{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Memory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int offset, int height, int width, int pitch)
{
return new Memory2D<T>(memory, offset, height, width, pitch);
}
#endif
/// <summary>
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Memory{T}"/> of bytes.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="Memory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Memory{T}"/> of bytes.</returns>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="Memory{T}.Length"/> property of the new <see cref="Memory{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<byte> AsBytes<T>(this Memory<T> memory)
where T : unmanaged
{
return MemoryMarshal.AsMemory(((ReadOnlyMemory<T>)memory).Cast<T, byte>());
}
/// <summary>
/// Casts a <see cref="Memory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="Memory{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="Memory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="Memory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="Memory{T}"/> of type <typeparamref name="TTo"/></returns>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory)
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.AsMemory(((ReadOnlyMemory<TFrom>)memory).Cast<TFrom, TTo>());
}
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>

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

@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr DangerousGetObjectDataByteOffset<T>(this object obj, ref T data)
{
var rawObj = Unsafe.As<RawObjectData>(obj);
var rawObj = Unsafe.As<RawObjectData>(obj)!;
ref byte r0 = ref rawObj.Data;
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
@ -55,7 +55,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetObjectDataReferenceAt<T>(this object obj, IntPtr offset)
{
var rawObj = Unsafe.As<RawObjectData>(obj);
var rawObj = Unsafe.As<RawObjectData>(obj)!;
ref byte r0 = ref rawObj.Data;
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
ref T r2 = ref Unsafe.As<byte, T>(ref r1);

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

@ -3,8 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
using Microsoft.Toolkit.HighPerformance.Buffers.Internals.Interfaces;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@ -14,6 +22,123 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </summary>
public static class ReadOnlyMemoryExtensions
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width)
{
return new ReadOnlyMemory2D<T>(memory, height, width);
}
/// <summary>
/// Returns a <see cref="ReadOnlyMemory2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlyMemory{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlyMemory{T}"/> instance.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
{
return new ReadOnlyMemory2D<T>(memory, offset, height, width, pitch);
}
#endif
/// <summary>
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="ReadOnlyMemory{T}"/> of bytes.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of bytes.</returns>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="ReadOnlyMemory{T}.Length"/> property of the new <see cref="ReadOnlyMemory{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory)
where T : unmanaged
{
return Cast<T, byte>(memory);
}
/// <summary>
/// Casts a <see cref="ReadOnlyMemory{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlyMemory{T}"/>.</typeparam>
/// <param name="memory">The source <see cref="ReadOnlyMemory{T}"/>, of type <typeparamref name="TFrom"/>.</param>
/// <returns>A <see cref="ReadOnlyMemory{T}"/> of type <typeparamref name="TTo"/></returns>
/// <exception cref="ArgumentException">Thrown when the data store of <paramref name="memory"/> is not supported.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory)
where TFrom : unmanaged
where TTo : unmanaged
{
if (memory.IsEmpty)
{
return default;
}
if (typeof(TFrom) == typeof(char) &&
MemoryMarshal.TryGetString((ReadOnlyMemory<char>)(object)memory, out string? text, out int start, out int length))
{
return new StringMemoryManager<TTo>(text!, start, length).Memory;
}
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<TFrom> segment))
{
return new ArrayMemoryManager<TFrom, TTo>(segment.Array!, segment.Offset, segment.Count).Memory;
}
if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(memory, out var memoryManager, out start, out length))
{
// If the memory manager is the one resulting from a previous cast, we can use it directly to retrieve
// a new manager for the target type that wraps the original data store, instead of creating one that
// wraps the current manager. This ensures that doing repeated casts always results in only up to one
// indirection level in the chain of memory managers needed to access the target data buffer to use.
if (memoryManager is IMemoryManager wrappingManager)
{
return wrappingManager.GetMemory<TTo>(start, length);
}
return new ProxyMemoryManager<TFrom, TTo>(memoryManager, start, length).Memory;
}
// Throws when the memory instance has an unsupported backing store
static ReadOnlyMemory<TTo> ThrowArgumentExceptionForUnsupportedMemory()
{
throw new ArgumentException("The input instance doesn't have a supported underlying data store.");
}
return ThrowArgumentExceptionForUnsupportedMemory();
}
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
/// </summary>
@ -27,6 +152,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </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);

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

@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@ -40,10 +43,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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 unsafe ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, int i)
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 an unsafe cast to
// force the JIT to skip the sign extension when going from int to native int.
// 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]
@ -54,7 +57,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
// 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 (IntPtr)(void*)(uint) cast, we get the following codegen instead:
// With the (nint)(uint) cast, we get the following codegen instead:
// =============================
// L0000: mov rax, [rcx]
// L0003: mov edx, edx
@ -67,13 +70,31 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
// 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 void*, which lets the following IntPtr cast avoid the range check on 32 bit
// (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
// 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, (IntPtr)(void*)(uint)i);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <see cref="ReadOnlySpan{T}"/>, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this ReadOnlySpan<T> span, nint i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
@ -135,17 +156,63 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
// 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 = Unsafe.As<bool, byte>(ref isInRange);
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, (IntPtr)(void*)offset);
ref T r1 = ref Unsafe.Add(ref r0, (nint)offset);
return ref r1;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int height, int width)
{
return new ReadOnlySpan2D<T>(span, height, width);
}
/// <summary>
/// Returns a <see cref="ReadOnlySpan2D{T}"/> instance wrapping the underlying data for the given <see cref="ReadOnlySpan{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="ReadOnlySpan{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="ReadOnlySpan2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int offset, int height, int width, int pitch)
{
return new ReadOnlySpan2D<T>(span, offset, height, width, pitch);
}
#endif
/// <summary>
/// Gets the index of an element of a given <see cref="ReadOnlySpan{T}"/> from its reference.
/// </summary>
@ -156,34 +223,20 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf<T>(this ReadOnlySpan<T> span, in T value)
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);
if (sizeof(IntPtr) == sizeof(long))
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
if ((nuint)elementOffset >= (uint)span.Length)
{
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
if ((ulong)elementOffset >= (ulong)span.Length)
{
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return unchecked((int)elementOffset);
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
else
{
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
if ((uint)elementOffset >= (uint)span.Length)
{
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return elementOffset;
}
return (int)elementOffset;
}
/// <summary>
@ -195,25 +248,21 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count<T>(this ReadOnlySpan<T> span, T value)
public static int Count<T>(this ReadOnlySpan<T> span, T value)
where T : IEquatable<T>
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)(void*)(uint)span.Length;
nint length = (nint)(uint)span.Length;
return SpanHelper.Count(ref r0, length, value);
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.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="ReadOnlySpan{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="ReadOnlySpan{T}.Length"/> property of the new <see cref="ReadOnlySpan{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
@ -227,7 +276,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="ReadOnlySpan{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="ReadOnlySpan{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="ReadOnlySpan{T}"/>.</typeparam>
@ -236,14 +284,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(this ReadOnlySpan<TFrom> span)
where TFrom : struct
where TTo : struct
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
@ -321,13 +366,41 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
public static int GetDjb2HashCode<T>(this ReadOnlySpan<T> span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)(void*)(uint)span.Length;
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);
}
}
}

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

@ -8,6 +8,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
@ -40,24 +43,84 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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 unsafe ref T DangerousGetReferenceAt<T>(this Span<T> span, int i)
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, (IntPtr)(void*)(uint)i);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
/// <summary>
/// Returns a reference to an element at a specified index within a given <see cref="Span{T}"/>, with no bounds checks.
/// </summary>
/// <typeparam name="T">The type of elements in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="i">The index of the element to retrieve within <paramref name="span"/>.</param>
/// <returns>A reference to the element within <paramref name="span"/> at the index specified by <paramref name="i"/>.</returns>
/// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt<T>(this Span<T> span, nint i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int height, int width)
{
return new Span2D<T>(span, height, width);
}
/// <summary>
/// Returns a <see cref="Span2D{T}"/> instance wrapping the underlying data for the given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items in the input <see cref="Span{T}"/> instance.</typeparam>
/// <param name="span">The input <see cref="Span{T}"/> instance.</param>
/// <param name="offset">The initial offset within <paramref name="span"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <returns>The resulting <see cref="Span2D{T}"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="span"/>.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int offset, int height, int width, int pitch)
{
return new Span2D<T>(span, offset, height, width, pitch);
}
#endif
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="T"/> to <see cref="Span{T}"/> of bytes.
/// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="T">The type if items in the source <see cref="Span{T}"/>.</typeparam>
/// <param name="span">The source slice, of type <typeparamref name="T"/>.</param>
/// <returns>A <see cref="Span{T}"/> of bytes.</returns>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="T"/> contains pointers.
/// </exception>
/// <exception cref="OverflowException">
/// Thrown if the <see cref="Span{T}.Length"/> property of the new <see cref="Span{T}"/> would exceed <see cref="int.MaxValue"/>.
/// </exception>
@ -71,7 +134,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <summary>
/// Casts a <see cref="Span{T}"/> of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
/// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety.
/// </summary>
/// <typeparam name="TFrom">The type of items in the source <see cref="Span{T}"/>.</typeparam>
/// <typeparam name="TTo">The type of items in the destination <see cref="Span{T}"/>.</typeparam>
@ -80,14 +142,11 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<TTo> Cast<TFrom, TTo>(this Span<TFrom> span)
where TFrom : struct
where TTo : struct
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.Cast<TFrom, TTo>(span);
}
@ -102,33 +161,19 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="value"/> does not belong to <paramref name="span"/>.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf<T>(this Span<T> span, ref T value)
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);
if (sizeof(IntPtr) == sizeof(long))
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf<T>();
if ((nuint)elementOffset >= (uint)span.Length)
{
long elementOffset = (long)byteOffset / Unsafe.SizeOf<T>();
if ((ulong)elementOffset >= (ulong)span.Length)
{
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return unchecked((int)elementOffset);
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
else
{
int elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
if ((uint)elementOffset >= (uint)span.Length)
{
ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return elementOffset;
}
return (int)elementOffset;
}
/// <summary>
@ -140,13 +185,13 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count<T>(this Span<T> span, T value)
public static int Count<T>(this Span<T> span, T value)
where T : IEquatable<T>
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)(void*)(uint)span.Length;
nint length = (nint)(uint)span.Length;
return SpanHelper.Count(ref r0, length, value);
return (int)SpanHelper.Count(ref r0, length, value);
}
/// <summary>
@ -211,15 +256,43 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetDjb2HashCode<T>(this Span<T> span)
public static int GetDjb2HashCode<T>(this Span<T> span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
IntPtr length = (IntPtr)(void*)(uint)span.Length;
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>

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

@ -167,24 +167,24 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The <typeparamref name="T"/> value read from <paramref name="stream"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown if <paramref name="stream"/> reaches the end.</exception>
#if SPAN_RUNTIME_SUPPORT
// Avoid inlining as we're renting a stack buffer, which
// cause issues if this method was called inside a loop
[MethodImpl(MethodImplOptions.NoInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static T Read<T>(this Stream stream)
where T : unmanaged
{
#if SPAN_RUNTIME_SUPPORT
Span<byte> span = stackalloc byte[Unsafe.SizeOf<T>()];
T result = default;
int length = Unsafe.SizeOf<T>();
if (stream.Read(span) != span.Length)
unsafe
{
ThrowInvalidOperationExceptionForEndOfStream();
if (stream.Read(new Span<byte>(&result, length)) != length)
{
ThrowInvalidOperationExceptionForEndOfStream();
}
}
ref byte r0 = ref MemoryMarshal.GetReference(span);
return Unsafe.ReadUnaligned<T>(ref r0);
return result;
#else
int length = Unsafe.SizeOf<T>();
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);

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

@ -5,7 +5,7 @@
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if !NETCOREAPP3_1
#if NETCOREAPP2_1 || NETSTANDARD
using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Enumerables;
@ -28,10 +28,10 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref char DangerousGetReference(this string text)
{
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
return ref Unsafe.AsRef(text.GetPinnableReference());
#elif NETCOREAPP2_1
var stringData = Unsafe.As<RawStringData>(text);
var stringData = Unsafe.As<RawStringData>(text)!;
return ref stringData.Data;
#else
@ -48,16 +48,16 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <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 unsafe ref char DangerousGetReferenceAt(this string text, int i)
public static ref char DangerousGetReferenceAt(this string text, int i)
{
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
#elif NETCOREAPP2_1
ref char r0 = ref Unsafe.As<RawStringData>(text).Data;
ref char r0 = ref Unsafe.As<RawStringData>(text)!.Data;
#else
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
#endif
ref char ri = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)i);
ref char ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
@ -91,12 +91,12 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <returns>The number of occurrences of <paramref name="c"/> in <paramref name="text"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count(this string text, char c)
public static int Count(this string text, char c)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)text.Length;
nint length = (nint)(uint)text.Length;
return SpanHelper.Count(ref r0, length, c);
return (int)SpanHelper.Count(ref r0, length, c);
}
/// <summary>
@ -160,7 +160,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static unsafe int GetDjb2HashCode(this string text)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)(void*)(uint)text.Length;
nint length = (nint)(uint)text.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}

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

@ -4,7 +4,7 @@
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
using System.Runtime.Intrinsics.X86;
#endif
@ -28,7 +28,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasFlag(uint value, int n)
public static unsafe bool HasFlag(uint value, int n)
{
// Read the n-th bit, downcast to byte
byte flag = (byte)((value >> n) & 1);
@ -40,7 +40,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
// compared the previous computed flag against 0, the assembly
// would have had to perform the test, set the non-zero
// flag and then extend the (byte) result to eax.
return Unsafe.As<byte, bool>(ref flag);
return *(bool*)&flag;
}
/// <summary>
@ -74,7 +74,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasLookupFlag(uint table, int x, int min = 0)
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.
@ -91,14 +91,14 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
// as a bool just like in the HasFlag method above, and then returned.
int i = x - min;
bool isInRange = (uint)i < 32u;
byte byteFlag = Unsafe.As<bool, byte>(ref isInRange);
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 = Unsafe.As<byte, bool>(ref result);
bool valid = *(bool*)&result;
return valid;
}
@ -194,7 +194,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint SetFlag(uint value, int n, bool flag)
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.
@ -210,7 +210,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
// operation. This will always guaranteed to work, thanks to the
// initial code clearing that bit before setting it again.
uint
flag32 = Unsafe.As<bool, byte>(ref flag),
flag32 = *(byte*)&flag,
shift = flag32 << n,
or = and | shift;
@ -235,7 +235,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ExtractRange(uint value, byte start, byte length)
{
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
if (Bmi1.IsSupported)
{
return Bmi1.BitFieldExtract(value, start, length);
@ -283,7 +283,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
loadMask = highBits << start,
storeMask = (flags & highBits) << start;
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
if (Bmi1.IsSupported)
{
return Bmi1.AndNot(loadMask, value) | storeMask;
@ -306,12 +306,12 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasFlag(ulong value, int n)
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 Unsafe.As<byte, bool>(ref flag);
return *(bool*)&flag;
}
/// <summary>
@ -328,18 +328,18 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasLookupFlag(ulong table, int x, int min = 0)
public static unsafe bool HasLookupFlag(ulong table, int x, int min = 0)
{
int i = x - min;
bool isInRange = (uint)i < 64u;
byte byteFlag = Unsafe.As<bool, byte>(ref isInRange);
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 = Unsafe.As<byte, bool>(ref result);
bool valid = *(bool*)&result;
return valid;
}
@ -373,13 +373,13 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong SetFlag(ulong value, int n, bool flag)
public static unsafe ulong SetFlag(ulong value, int n, bool flag)
{
ulong
bit = 1ul << n,
not = ~bit,
and = value & not,
flag64 = Unsafe.As<bool, byte>(ref flag),
flag64 = *(byte*)&flag,
shift = flag64 << n,
or = and | shift;
@ -404,7 +404,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ExtractRange(ulong value, byte start, byte length)
{
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
if (Bmi1.X64.IsSupported)
{
return Bmi1.X64.BitFieldExtract(value, start, length);
@ -452,7 +452,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
loadMask = highBits << start,
storeMask = (flags & highBits) << start;
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
if (Bmi1.X64.IsSupported)
{
return Bmi1.X64.AndNot(loadMask, value) | storeMask;

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

@ -9,6 +9,11 @@ using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if SPAN_RUNTIME_SUPPORT
using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
@ -25,14 +30,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// For more info, see <see href="https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode#remarks"/>.
/// </remarks>
public struct HashCode<T>
#if SPAN_RUNTIME_SUPPORT
where T : notnull
#else
// If we lack the RuntimeHelpers.IsReferenceOrContainsReferences<T> API,
// we need to constraint the generic type parameter to unmanaged, as we
// wouldn't otherwise be able to properly validate it at runtime.
where T : unmanaged
#endif
{
/// <summary>
/// Gets a content hash from the input <see cref="ReadOnlySpan{T}"/> instance using the xxHash32 algorithm.
@ -57,19 +55,17 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// <remarks>The returned hash code is not processed through <see cref="HashCode"/> APIs.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe int CombineValues(ReadOnlySpan<T> span)
internal static int CombineValues(ReadOnlySpan<T> span)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
#if SPAN_RUNTIME_SUPPORT
// If typeof(T) is not unmanaged, iterate over all the items one by one.
// This check is always known in advance either by the JITter or by the AOT
// compiler, so this branch will never actually be executed by the code.
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
return SpanHelper.GetDjb2HashCode(ref r0, (IntPtr)(void*)(uint)span.Length);
return SpanHelper.GetDjb2HashCode(ref r0, (nint)(uint)span.Length);
}
#endif
// Get the info for the target memory area to process.
// The line below is computing the total byte size for the span,
@ -79,7 +75,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
// process. In that case it will just compute the byte size as a 32 bit
// multiplication with overflow, which is guaranteed never to happen anyway.
ref byte rb = ref Unsafe.As<T, byte>(ref r0);
IntPtr length = (IntPtr)(void*)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
nint length = (nint)((uint)span.Length * (uint)Unsafe.SizeOf<T>());
return SpanHelper.GetDjb2LikeByteHash(ref rb, length);
}

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

@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
#if NETCOREAPP3_1 || NET5_0
using static System.Numerics.BitOperations;
#endif
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
{
/// <summary>
/// Utility methods for intrinsic bit-twiddling operations. The methods use hardware intrinsics
/// when available on the underlying platform, otherwise they use optimized software fallbacks.
/// </summary>
internal static class BitOperations
{
/// <summary>
/// Rounds up an <see cref="int"/> value to a power of 2.
/// </summary>
/// <param name="x">The input value to round up.</param>
/// <returns>The smallest power of two greater than or equal to <paramref name="x"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundUpPowerOfTwo(int x)
{
#if NETCOREAPP3_1 || NET5_0
return 1 << (32 - LeadingZeroCount((uint)(x - 1)));
#else
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
#endif
}
}
}

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

@ -0,0 +1,267 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
{
/// <summary>
/// Helpers to process sequences of values by reference with a given step.
/// </summary>
internal static class RefEnumerableHelper
{
/// <summary>
/// Clears a target memory area.
/// </summary>
/// <typeparam name="T">The type of values to clear.</typeparam>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
/// <param name="length">The number of items in the memory area.</param>
/// <param name="step">The number of items between each consecutive target value.</param>
public static void Clear<T>(ref T r0, nint length, nint step)
{
nint offset = 0;
// Main loop with 8 unrolled iterations
while (length >= 8)
{
Unsafe.Add(ref r0, offset) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
length -= 8;
offset += step;
}
if (length >= 4)
{
Unsafe.Add(ref r0, offset) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
Unsafe.Add(ref r0, offset += step) = default!;
length -= 4;
offset += step;
}
// Clear the remaining values
while (length > 0)
{
Unsafe.Add(ref r0, offset) = default!;
length -= 1;
offset += step;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
{
nint
sourceOffset = 0,
destinationOffset = 0;
while (length >= 8)
{
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
length -= 8;
sourceOffset += sourceStep;
destinationOffset += 8;
}
if (length >= 4)
{
Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
length -= 4;
sourceOffset += sourceStep;
destinationOffset += 4;
}
while (length > 0)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
length -= 1;
sourceOffset += sourceStep;
destinationOffset += 1;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
/// <param name="destinationStep">The step between consecutive items in the memory area pointed to by <paramref name="destinationRef"/>.</param>
public static void CopyTo<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep, nint destinationStep)
{
nint
sourceOffset = 0,
destinationOffset = 0;
while (length >= 8)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
length -= 8;
sourceOffset += sourceStep;
destinationOffset += destinationStep;
}
if (length >= 4)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
Unsafe.Add(ref destinationRef, destinationOffset += destinationStep) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep);
length -= 4;
sourceOffset += sourceStep;
destinationOffset += destinationStep;
}
while (length > 0)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
length -= 1;
sourceOffset += sourceStep;
destinationOffset += destinationStep;
}
}
/// <summary>
/// Copies a sequence of discontiguous items from one memory area to another. This mirrors
/// <see cref="CopyTo{T}(ref T,ref T,nint,nint)"/>, but <paramref name="sourceStep"/> refers to <paramref name="destinationRef"/> instead.
/// </summary>
/// <typeparam name="T">The type of items to copy.</typeparam>
/// <param name="sourceRef">The source reference to copy from.</param>
/// <param name="destinationRef">The target reference to copy to.</param>
/// <param name="length">The total number of items to copy.</param>
/// <param name="sourceStep">The step between consecutive items in the memory area pointed to by <paramref name="sourceRef"/>.</param>
public static void CopyFrom<T>(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep)
{
nint
sourceOffset = 0,
destinationOffset = 0;
while (length >= 8)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 4);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 5);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 6);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 7);
length -= 8;
sourceOffset += 8;
destinationOffset += sourceStep;
}
if (length >= 4)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 1);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 2);
Unsafe.Add(ref destinationRef, destinationOffset += sourceStep) = Unsafe.Add(ref sourceRef, sourceOffset + 3);
length -= 4;
sourceOffset += 4;
destinationOffset += sourceStep;
}
while (length > 0)
{
Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset);
length -= 1;
sourceOffset += 1;
destinationOffset += sourceStep;
}
}
/// <summary>
/// Fills a target memory area.
/// </summary>
/// <typeparam name="T">The type of values to fill.</typeparam>
/// <param name="r0">A <typeparamref name="T"/> reference to the start of the memory area.</param>
/// <param name="length">The number of items in the memory area.</param>
/// <param name="step">The number of items between each consecutive target value.</param>
/// <param name="value">The value to assign to every item in the target memory area.</param>
public static void Fill<T>(ref T r0, nint length, nint step, T value)
{
nint offset = 0;
while (length >= 8)
{
Unsafe.Add(ref r0, offset) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
length -= 8;
offset += step;
}
if (length >= 4)
{
Unsafe.Add(ref r0, offset) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
Unsafe.Add(ref r0, offset += step) = value;
length -= 4;
offset += step;
}
while (length > 0)
{
Unsafe.Add(ref r0, offset) = value;
length -= 1;
offset += step;
}
}
}
}

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

@ -0,0 +1,305 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable SA1512
// The portable implementation in this type is originally from CoreFX.
// See https://github.com/dotnet/corefx/blob/release/2.1/src/System.Memory/src/System/SpanHelpers.cs.
using System;
using System.Diagnostics.Contracts;
#if !SPAN_RUNTIME_SUPPORT
using System.Reflection;
#endif
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
{
/// <summary>
/// A helper class that with utility methods for dealing with references, and other low-level details.
/// It also contains some APIs that act as polyfills for .NET Standard 2.0 and below.
/// </summary>
internal static class RuntimeHelpers
{
/// <summary>
/// Converts a length of items from one size to another (rounding towards zero).
/// </summary>
/// <typeparam name="TFrom">The source type of items.</typeparam>
/// <typeparam name="TTo">The target type of items.</typeparam>
/// <param name="length">The input length to convert.</param>
/// <returns>The converted length for the specified argument and types.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ConvertLength<TFrom, TTo>(int length)
where TFrom : unmanaged
where TTo : unmanaged
{
if (sizeof(TFrom) == sizeof(TTo))
{
return length;
}
else if (sizeof(TFrom) == 1)
{
return (int)((uint)length / (uint)sizeof(TTo));
}
else
{
ulong targetLength = (ulong)(uint)length * (uint)sizeof(TFrom) / (uint)sizeof(TTo);
return checked((int)targetLength);
}
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
/// <remarks>
/// This method is needed because this expression is not inlined correctly if the target array
/// is only visible as a non-generic <see cref="Array"/> instance, because the C# compiler will
/// not be able to emit the <see langword="ldlen"/> opcode instead of calling the right method.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength<T>(T[] array)
{
#if NETSTANDARD1_4
// .NET Standard 1.4 doesn't include the API to get the long length, so
// we just cast the length and throw in case the array is larger than
// int.MaxValue. There's not much we can do in this specific case.
return (nint)(uint)array.Length;
#else
return (nint)array.LongLength;
#endif
}
/// <summary>
/// Gets the length of a given array as a native integer.
/// </summary>
/// <param name="array">The input <see cref="Array"/> instance.</param>
/// <returns>The total length of <paramref name="array"/> as a native integer.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint GetArrayNativeLength(Array array)
{
#if NETSTANDARD1_4
return (nint)(uint)array.Length;
#else
return (nint)array.LongLength;
#endif
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a SZ array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArrayDataByteOffset<T>()
{
return TypeInfo<T>.ArrayDataByteOffset;
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 2D array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray2DDataByteOffset<T>()
{
return TypeInfo<T>.Array2DDataByteOffset;
}
/// <summary>
/// Gets the byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
/// <returns>The byte offset to the first <typeparamref name="T"/> element in a 3D array.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IntPtr GetArray3DDataByteOffset<T>()
{
return TypeInfo<T>.Array3DDataByteOffset;
}
#if !SPAN_RUNTIME_SUPPORT
/// <summary>
/// Gets a byte offset describing a portable pinnable reference. This can either be an
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
/// and a reference to some of its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a reference that is assumed to refer to pinned data).
/// </summary>
/// <typeparam name="T">The type of field being referenced.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
/// <returns>
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
/// for the parameter <paramref name="obj"/>, or the value of the raw pointer passed as a tracked reference.
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe IntPtr GetObjectDataOrReferenceByteOffset<T>(object? obj, ref T data)
{
if (obj is null)
{
return (IntPtr)Unsafe.AsPointer(ref data);
}
return obj.DangerousGetObjectDataByteOffset(ref data);
}
/// <summary>
/// Gets a reference from data describing a portable pinnable reference. This can either be an
/// interior pointer into some object data (described with a valid <see cref="object"/> reference
/// and a byte offset into its data), or a raw pointer (described with a <see langword="null"/>
/// reference to an <see cref="object"/>, and a byte offset representing the value of the raw pointer).
/// </summary>
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
/// <returns>A <typeparamref name="T"/> reference matching the given parameters.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T GetObjectDataAtOffsetOrPointerReference<T>(object? obj, IntPtr offset)
{
if (obj is null)
{
return ref Unsafe.AsRef<T>((void*)offset);
}
return ref obj.DangerousGetObjectDataReferenceAt<T>(offset);
}
/// <summary>
/// Checks whether or not a given type is a reference type or contains references.
/// </summary>
/// <typeparam name="T">The type to check.</typeparam>
/// <returns>Whether or not <typeparamref name="T"/> respects the <see langword="unmanaged"/> constraint.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsReferenceOrContainsReferences<T>()
{
return TypeInfo<T>.IsReferenceOrContainsReferences;
}
/// <summary>
/// Implements the logic for <see cref="IsReferenceOrContainsReferences{T}"/>.
/// </summary>
/// <param name="type">The current type to check.</param>
/// <returns>Whether or not <paramref name="type"/> is a reference type or contains references.</returns>
[Pure]
private static bool IsReferenceOrContainsReferences(Type type)
{
// Common case, for primitive types
if (type.GetTypeInfo().IsPrimitive)
{
return false;
}
if (!type.GetTypeInfo().IsValueType)
{
return true;
}
// Check if the type is Nullable<T>
if (Nullable.GetUnderlyingType(type) is Type nullableType)
{
type = nullableType;
}
if (type.GetTypeInfo().IsEnum)
{
return false;
}
// Complex struct, recursively inspect all fields
foreach (FieldInfo field in type.GetTypeInfo().DeclaredFields)
{
if (field.IsStatic)
{
continue;
}
if (IsReferenceOrContainsReferences(field.FieldType))
{
return true;
}
}
return false;
}
#endif
/// <summary>
/// A private generic class to preload type info for arbitrary runtime types.
/// </summary>
/// <typeparam name="T">The type to load info for.</typeparam>
private static class TypeInfo<T>
{
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a SZ array.
/// </summary>
public static readonly IntPtr ArrayDataByteOffset = MeasureArrayDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 2D array.
/// </summary>
public static readonly IntPtr Array2DDataByteOffset = MeasureArray2DDataByteOffset();
/// <summary>
/// The byte offset to the first <typeparamref name="T"/> element in a 3D array.
/// </summary>
public static readonly IntPtr Array3DDataByteOffset = MeasureArray3DDataByteOffset();
#if !SPAN_RUNTIME_SUPPORT
/// <summary>
/// Indicates whether <typeparamref name="T"/> does not respect the <see langword="unmanaged"/> constraint.
/// </summary>
public static readonly bool IsReferenceOrContainsReferences = IsReferenceOrContainsReferences(typeof(T));
#endif
/// <summary>
/// Computes the value for <see cref="ArrayDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="ArrayDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArrayDataByteOffset()
{
var array = new T[1];
return array.DangerousGetObjectDataByteOffset(ref array[0]);
}
/// <summary>
/// Computes the value for <see cref="Array2DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array2DDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArray2DDataByteOffset()
{
var array = new T[1, 1];
return array.DangerousGetObjectDataByteOffset(ref array[0, 0]);
}
/// <summary>
/// Computes the value for <see cref="Array3DDataByteOffset"/>.
/// </summary>
/// <returns>The value of <see cref="Array3DDataByteOffset"/> for the current runtime.</returns>
[Pure]
private static IntPtr MeasureArray3DDataByteOffset()
{
var array = new T[1, 1, 1];
return array.DangerousGetObjectDataByteOffset(ref array[0, 0, 0]);
}
}
}
}

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

@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@ -25,7 +25,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// <returns>The number of occurrences of <paramref name="value"/> in the search space</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count<T>(ref T r0, IntPtr length, T value)
public static nint Count<T>(ref T r0, nint length, T value)
where T : IEquatable<T>
{
if (!Vector.IsHardwareAccelerated)
@ -41,7 +41,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref sbyte r1 = ref Unsafe.As<T, sbyte>(ref r0);
sbyte target = Unsafe.As<T, sbyte>(ref value);
return CountSimd(ref r1, length, target, (IntPtr)sbyte.MaxValue);
return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(char) ||
@ -51,7 +51,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref short r1 = ref Unsafe.As<T, short>(ref r0);
short target = Unsafe.As<T, short>(ref value);
return CountSimd(ref r1, length, target, (IntPtr)short.MaxValue);
return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(int) ||
@ -60,7 +60,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref int r1 = ref Unsafe.As<T, int>(ref r0);
int target = Unsafe.As<T, int>(ref value);
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
return CountSimd(ref r1, length, target);
}
if (typeof(T) == typeof(long) ||
@ -69,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
ref long r1 = ref Unsafe.As<T, long>(ref r0);
long target = Unsafe.As<T, long>(ref value);
return CountSimd(ref r1, length, target, (IntPtr)int.MaxValue);
return CountSimd(ref r1, length, target);
}
return CountSequential(ref r0, length, value);
@ -79,47 +79,44 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// Implements <see cref="Count{T}"/> with a sequential search.
/// </summary>
[Pure]
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
private static unsafe int CountSequential<T>(ref T r0, IntPtr length, T value)
private static nint CountSequential<T>(ref T r0, nint length, T value)
where T : IEquatable<T>
{
int result = 0;
IntPtr offset = default;
nint
result = 0,
offset = 0;
// Main loop with 8 unrolled iterations
while ((byte*)length >= (byte*)8)
while (length >= 8)
{
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
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 ((byte*)length >= (byte*)4)
if (length >= 4)
{
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
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 ((byte*)length > (byte*)0)
while (length > 0)
{
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
length -= 1;
offset += 1;
@ -132,18 +129,15 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// Implements <see cref="Count{T}"/> with a vectorized search.
/// </summary>
[Pure]
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
private static unsafe int CountSimd<T>(ref T r0, IntPtr length, T value, IntPtr max)
private static nint CountSimd<T>(ref T r0, nint length, T value)
where T : unmanaged, IEquatable<T>
{
int result = 0;
IntPtr offset = default;
nint
result = 0,
offset = 0;
// Skip the initialization overhead if there are not enough items
if ((byte*)length >= (byte*)Vector<T>.Count)
if (length >= Vector<T>.Count)
{
var vc = new Vector<T>(value);
@ -154,13 +148,75 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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.
IntPtr
chunkLength = Min(length, max),
nint
max = GetUpperBound<T>(),
chunkLength = length <= max ? length : max,
initialOffset = offset;
var partials = Vector<T>.Zero;
while ((byte*)chunkLength >= (byte*)Vector<T>.Count)
// Unrolled vectorized loop, with 8 unrolled iterations. We only run this when the
// current type T is at least 2 bytes in size, otherwise the average chunk length
// would always be too small to be able to trigger the unrolled loop, and the overall
// performance would just be slightly worse due to the additional conditional branches.
if (typeof(T) != typeof(sbyte))
{
while (chunkLength >= Vector<T>.Count * 8)
{
ref T ri0 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 0));
var vi0 = Unsafe.As<T, Vector<T>>(ref ri0);
var ve0 = Vector.Equals(vi0, vc);
partials -= ve0;
ref T ri1 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 1));
var vi1 = Unsafe.As<T, Vector<T>>(ref ri1);
var ve1 = Vector.Equals(vi1, vc);
partials -= ve1;
ref T ri2 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 2));
var vi2 = Unsafe.As<T, Vector<T>>(ref ri2);
var ve2 = Vector.Equals(vi2, vc);
partials -= ve2;
ref T ri3 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 3));
var vi3 = Unsafe.As<T, Vector<T>>(ref ri3);
var ve3 = Vector.Equals(vi3, vc);
partials -= ve3;
ref T ri4 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 4));
var vi4 = Unsafe.As<T, Vector<T>>(ref ri4);
var ve4 = Vector.Equals(vi4, vc);
partials -= ve4;
ref T ri5 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 5));
var vi5 = Unsafe.As<T, Vector<T>>(ref ri5);
var ve5 = Vector.Equals(vi5, vc);
partials -= ve5;
ref T ri6 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 6));
var vi6 = Unsafe.As<T, Vector<T>>(ref ri6);
var ve6 = Vector.Equals(vi6, vc);
partials -= ve6;
ref T ri7 = ref Unsafe.Add(ref r0, offset + (Vector<T>.Count * 7));
var vi7 = Unsafe.As<T, Vector<T>>(ref ri7);
var ve7 = Vector.Equals(vi7, vc);
partials -= ve7;
chunkLength -= Vector<T>.Count * 8;
offset += Vector<T>.Count * 8;
}
}
while (chunkLength >= Vector<T>.Count)
{
ref T ri = ref Unsafe.Add(ref r0, offset);
@ -181,27 +237,26 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += Vector<T>.Count;
}
result += CastToInt(Vector.Dot(partials, Vector<T>.One));
length = Subtract(length, Subtract(offset, initialOffset));
result += CastToNativeInt(Vector.Dot(partials, Vector<T>.One));
length -= offset - initialOffset;
}
while ((byte*)length >= (byte*)Vector<T>.Count);
while (length >= Vector<T>.Count);
}
// Optional 8 unrolled iterations. This is only done when a single SIMD
// register can contain over 8 values of the current type, as otherwise
// there could never be enough items left after the vectorized path
if (Vector<T>.Count > 8 &&
(byte*)length >= (byte*)8)
length >= 8)
{
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 4).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 5).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 6).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 7).Equals(value).ToInt();
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;
@ -209,21 +264,21 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// Optional 4 unrolled iterations
if (Vector<T>.Count > 4 &&
(byte*)length >= (byte*)4)
length >= 4)
{
result += Unsafe.Add(ref r0, offset + 0).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 1).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 2).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset + 3).Equals(value).ToInt();
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 ((byte*)length > (byte*)0)
while (length > 0)
{
result += Unsafe.Add(ref r0, offset).Equals(value).ToInt();
result += Unsafe.Add(ref r0, offset).Equals(value).ToByte();
length -= 1;
offset += 1;
@ -233,73 +288,82 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
/// <summary>
/// Returns the minimum between two <see cref="IntPtr"/> values.
/// Gets the upper bound for partial sums with a given <typeparamref name="T"/> parameter.
/// </summary>
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
/// <param name="b">The second <see cref="IntPtr"/> value</param>
/// <returns>The minimum between <paramref name="a"/> and <paramref name="b"/>.</returns>
/// <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 IntPtr Min(IntPtr a, IntPtr b)
{
if (sizeof(IntPtr) == 4)
{
return (IntPtr)Math.Min((int)a, (int)b);
}
return (IntPtr)Math.Min((long)a, (long)b);
}
/// <summary>
/// Returns the difference between two <see cref="IntPtr"/> values.
/// </summary>
/// <param name="a">The first <see cref="IntPtr"/> value.</param>
/// <param name="b">The second <see cref="IntPtr"/> value</param>
/// <returns>The difference between <paramref name="a"/> and <paramref name="b"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe IntPtr Subtract(IntPtr a, IntPtr b)
{
if (sizeof(IntPtr) == 4)
{
return (IntPtr)((int)a - (int)b);
}
return (IntPtr)((long)a - (long)b);
}
/// <summary>
/// Casts a value of a given type to <see cref="int"/>.
/// </summary>
/// <typeparam name="T">The input type to cast.</typeparam>
/// <param name="value">The input <typeparamref name="T"/> value to cast to <see cref="int"/>.</param>
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CastToInt<T>(T value)
private static unsafe nint GetUpperBound<T>()
where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
{
return Unsafe.As<T, sbyte>(ref value);
return sbyte.MaxValue;
}
if (typeof(T) == typeof(short))
{
return Unsafe.As<T, short>(ref value);
return short.MaxValue;
}
if (typeof(T) == typeof(int))
{
return Unsafe.As<T, int>(ref value);
return int.MaxValue;
}
if (typeof(T) == typeof(long))
{
return (int)Unsafe.As<T, long>(ref value);
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 new NotSupportedException($"Invalid input type {typeof(T)}");
throw null!;
}
/// <summary>
/// Casts a value of a given type to a native <see cref="int"/>.
/// </summary>
/// <typeparam name="T">The input type to cast.</typeparam>
/// <param name="value">The input <typeparamref name="T"/> value to cast to native <see cref="int"/>.</param>
/// <returns>The native <see cref="int"/> cast of <paramref name="value"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nint CastToNativeInt<T>(T value)
where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
{
return (byte)(sbyte)(object)value;
}
if (typeof(T) == typeof(short))
{
return (ushort)(short)(object)value;
}
if (typeof(T) == typeof(int))
{
return (nint)(uint)(int)(object)value;
}
if (typeof(T) == typeof(long))
{
return (nint)(ulong)(long)(object)value;
}
throw null!;
}
}
}

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

@ -1,8 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -22,17 +21,13 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// <param name="length">The number of items to hash.</param>
/// <returns>The Djb2 value for the input sequence of items.</returns>
[Pure]
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
public static unsafe int GetDjb2HashCode<T>(ref T r0, IntPtr length)
public static int GetDjb2HashCode<T>(ref T r0, nint length)
where T : notnull
{
int hash = 5381;
nint offset = 0;
IntPtr offset = default;
while ((byte*)length >= (byte*)8)
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
@ -52,7 +47,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += 8;
}
if ((byte*)length >= (byte*)4)
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());
@ -63,7 +58,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
offset += 4;
}
while ((byte*)length > (byte*)0)
while (length > 0)
{
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
@ -89,14 +84,10 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
/// faster than <see cref="GetDjb2HashCode{T}"/>, as it can parallelize much of the workload.
/// </remarks>
[Pure]
#if NETCOREAPP3_1
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
#endif
public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length)
public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length)
{
int hash = 5381;
IntPtr offset = default;
nint offset = 0;
// Check whether SIMD instructions are supported, and also check
// whether we have enough data to perform at least one unrolled
@ -107,7 +98,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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 &&
(byte*)length >= (byte*)(Vector<byte>.Count << 3))
length >= (Vector<byte>.Count << 3))
{
var vh = new Vector<int>(5381);
var v33 = new Vector<int>(33);
@ -115,7 +106,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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 ((byte*)length >= (byte*)(Vector<byte>.Count << 3))
while (length >= (Vector<byte>.Count << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector<byte>.Count * 0));
var vi0 = Unsafe.ReadUnaligned<Vector<int>>(ref ri0);
@ -163,7 +154,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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 ((byte*)length >= (byte*)Vector<byte>.Count)
while (length >= Vector<byte>.Count)
{
ref byte ri = ref Unsafe.Add(ref r0, offset);
var vi = Unsafe.ReadUnaligned<Vector<int>>(ref ri);
@ -186,9 +177,9 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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(IntPtr) == sizeof(ulong))
if (sizeof(nint) == sizeof(ulong))
{
while ((byte*)length >= (byte*)(sizeof(ulong) << 3))
while (length >= (sizeof(ulong) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0));
var value0 = Unsafe.ReadUnaligned<ulong>(ref ri0);
@ -228,7 +219,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
// Each unrolled iteration processes 32 bytes
while ((byte*)length >= (byte*)(sizeof(uint) << 3))
while (length >= (sizeof(uint) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0));
var value0 = Unsafe.ReadUnaligned<uint>(ref ri0);
@ -271,7 +262,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
// 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 ((byte*)length >= (byte*)(sizeof(ushort) << 3))
if (length >= (sizeof(ushort) << 3))
{
ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0));
var value0 = Unsafe.ReadUnaligned<ushort>(ref ri0);
@ -310,7 +301,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
}
// Handle the leftover items
while ((byte*)length > (byte*)0)
while (length > 0)
{
hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset));

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

@ -218,7 +218,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int

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

@ -312,7 +312,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int i)
{
int

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

@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Invoke(int i)
public void Invoke(int i)
{
int
low = i * this.batchSize,
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
Unsafe.AsRef(this.action).Invoke(rj);
}

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

@ -0,0 +1,160 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Memory;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, int minimumActionsPerThread)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action)
where TAction : struct, IInAction<TItem>
{
ForEach(memory, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IInAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
public static void ForEach<TItem, TAction>(ReadOnlyMemory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IInAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
nint
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
int
cores = Environment.ProcessorCount,
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
batchHeight = 1 + ((memory.Height - 1) / numBatches);
var actionInvoker = new InActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
// Skip the parallel invocation when possible
if (numBatches == 1)
{
actionInvoker.Invoke(0);
return;
}
// Run the batched operations in parallel
Parallel.For(
0,
numBatches,
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
actionInvoker.Invoke);
}
// Wrapping struct acting as explicit closure to execute the processing batches
private readonly struct InActionInvokerWithReadOnlyMemory2D<TItem, TAction>
where TAction : struct, IInAction<TItem>
{
private readonly int batchHeight;
private readonly ReadOnlyMemory2D<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public InActionInvokerWithReadOnlyMemory2D(
int batchHeight,
ReadOnlyMemory2D<TItem> memory,
in TAction action)
{
this.batchHeight = batchHeight;
this.memory = memory;
this.action = action;
}
/// <summary>
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
public void Invoke(int i)
{
int lowY = i * this.batchHeight;
nint highY = lowY + this.batchHeight;
int
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
width = this.memory.Width;
ReadOnlySpan2D<TItem> span = this.memory.Span;
for (int y = lowY; y < stopY; y++)
{
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
for (int x = 0; x < width; x++)
{
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
Unsafe.AsRef(this.action).Invoke(ryx);
}
}
}
}
}
}

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

@ -135,8 +135,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Invoke(int i)
public void Invoke(int i)
{
int
low = i * this.batchSize,
@ -147,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
for (int j = low; j < end; j++)
{
ref TItem rj = ref Unsafe.Add(ref r0, (IntPtr)(void*)(uint)j);
ref TItem rj = ref Unsafe.Add(ref r0, (nint)(uint)j);
Unsafe.AsRef(this.action).Invoke(ref rj);
}

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

@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.HighPerformance.Memory;
namespace Microsoft.Toolkit.HighPerformance.Helpers
{
/// <summary>
/// Helpers to work with parallel code in a highly optimized manner.
/// </summary>
public static partial class ParallelHelper
{
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, default(TAction), 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, int minimumActionsPerThread)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, default(TAction), minimumActionsPerThread);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action)
where TAction : struct, IRefAction<TItem>
{
ForEach(memory, action, 1);
}
/// <summary>
/// Executes a specified action in an optimized parallel loop over the input data.
/// </summary>
/// <typeparam name="TItem">The type of items to iterate over.</typeparam>
/// <typeparam name="TAction">The type of action (implementing <see cref="IRefAction{T}"/> of <typeparamref name="TItem"/>) to invoke over each item.</typeparam>
/// <param name="memory">The input <see cref="Memory2D{T}"/> representing the data to process.</param>
/// <param name="action">The <typeparamref name="TAction"/> instance representing the action to invoke.</param>
/// <param name="minimumActionsPerThread">
/// The minimum number of actions to run per individual thread. Set to 1 if all invocations
/// should be parallelized, or to a greater number if each individual invocation is fast
/// enough that it is more efficient to set a lower bound per each running thread.
/// </param>
public static void ForEach<TItem, TAction>(Memory2D<TItem> memory, in TAction action, int minimumActionsPerThread)
where TAction : struct, IRefAction<TItem>
{
if (minimumActionsPerThread <= 0)
{
ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread();
}
if (memory.IsEmpty)
{
return;
}
// The underlying data for a Memory2D<T> instance is bound to int.MaxValue in both
// axes, but its total size can exceed this value. Because of this, we calculate
// the target chunks as nint to avoid overflows, and switch back to int values
// for the rest of the setup, since the number of batches is bound to the number
// of CPU cores (which is an int), and the height of each batch is necessarily
// smaller than or equal than int.MaxValue, as it can't be greater than the
// number of total batches, which again is capped at the number of CPU cores.
nint
maxBatches = 1 + ((memory.Length - 1) / minimumActionsPerThread),
clipBatches = maxBatches <= memory.Height ? maxBatches : memory.Height;
int
cores = Environment.ProcessorCount,
numBatches = (int)(clipBatches <= cores ? clipBatches : cores),
batchHeight = 1 + ((memory.Height - 1) / numBatches);
var actionInvoker = new RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>(batchHeight, memory, action);
// Skip the parallel invocation when possible
if (numBatches == 1)
{
actionInvoker.Invoke(0);
return;
}
// Run the batched operations in parallel
Parallel.For(
0,
numBatches,
new ParallelOptions { MaxDegreeOfParallelism = numBatches },
actionInvoker.Invoke);
}
// Wrapping struct acting as explicit closure to execute the processing batches
private readonly struct RefActionInvokerWithReadOnlyMemory2D<TItem, TAction>
where TAction : struct, IRefAction<TItem>
{
private readonly int batchHeight;
private readonly Memory2D<TItem> memory;
private readonly TAction action;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefActionInvokerWithReadOnlyMemory2D(
int batchHeight,
Memory2D<TItem> memory,
in TAction action)
{
this.batchHeight = batchHeight;
this.memory = memory;
this.action = action;
}
/// <summary>
/// Processes the batch of actions at a specified index
/// </summary>
/// <param name="i">The index of the batch to process</param>
public void Invoke(int i)
{
int lowY = i * this.batchHeight;
nint highY = lowY + this.batchHeight;
int
stopY = (int)(highY <= this.memory.Height ? highY : this.memory.Height),
width = this.memory.Width;
ReadOnlySpan2D<TItem> span = this.memory.Span;
for (int y = lowY; y < stopY; y++)
{
ref TItem r0 = ref span.DangerousGetReferenceAt(y, 0);
for (int x = 0; x < width; x++)
{
ref TItem ryx = ref Unsafe.Add(ref r0, (nint)(uint)x);
Unsafe.AsRef(this.action).Invoke(ref ryx);
}
}
}
}
}
}

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

@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using static System.Math;
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
{
/// <summary>
/// A helper to validate arithmetic operations for <see cref="Memory2D{T}"/> and <see cref="Span2D{T}"/>.
/// </summary>
internal static class OverflowHelper
{
/// <summary>
/// Ensures that the input parameters will not exceed the maximum native int value when indexing.
/// </summary>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
/// <remarks>The input parameters are assumed to always be positive.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureIsInNativeIntRange(int height, int width, int pitch)
{
// As per the layout used in the Memory2D<T> and Span2D<T> types, we have the
// following memory representation with respect to height, width and pitch:
//
// _________width_________ ________...
// / \/
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |_
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_|
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// ...__pitch__/
//
// The indexing logic works on nint values in unchecked mode, with no overflow checks,
// which means it relies on the maximum element index to always be within <= nint.MaxValue.
// To ensure no overflows will ever occur there, we need to ensure that no instance can be
// created with parameters that could cause an overflow in case any item was accessed, so we
// need to ensure no overflows occurs when calculating the index of the last item in each view.
// The logic below calculates that index with overflow checks, throwing if one is detected.
// Note that we're subtracting 1 to the height as we don't want to include the trailing pitch
// for the 2D memory area, and also 1 to the width as the index is 0-based, as usual.
// Additionally, we're also ensuring that the stride is never greater than int.MaxValue, for
// consistency with how ND arrays work (int.MaxValue as upper bound for each axis), and to
// allow for faster iteration in the RefEnumerable<T> type, when traversing columns.
_ = checked(((nint)(width + pitch) * Max(unchecked(height - 1), 0)) + Max(unchecked(width - 1), 0));
}
/// <summary>
/// Ensures that the input parameters will not exceed <see cref="int.MaxValue"/> when indexing.
/// </summary>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map (the distance between each row).</param>
/// <returns>The area resulting from the given parameters.</returns>
/// <exception cref="OverflowException">Throw when the inputs don't fit in the expected range.</exception>
/// <remarks>The input parameters are assumed to always be positive.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeInt32Area(int height, int width, int pitch)
{
return checked(((width + pitch) * Max(unchecked(height - 1), 0)) + width);
}
}
}

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

@ -0,0 +1,122 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.HighPerformance.Memory.Internals
{
/// <summary>
/// A helper class to throw exceptions for memory types.
/// </summary>
internal static class ThrowHelper
{
/// <summary>
/// Throws an <see cref="ArgumentException"/> when using the <see langword="void"/>* constructor with a managed type.
/// </summary>
public static void ThrowArgumentExceptionForManagedType()
{
throw new ArgumentException("Can't use a void* constructor when T is a managed type");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>
public static void ThrowArgumentExceptionForDestinationTooShort()
{
throw new ArgumentException("The target span is too short to copy all the current items to");
}
/// <summary>
/// Throws an <see cref="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");
}
}
}

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

@ -0,0 +1,905 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
using Microsoft.Toolkit.HighPerformance.Memory.Views;
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#pragma warning disable CA2231
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <summary>
/// <see cref="Memory2D{T}"/> represents a 2D region of arbitrary memory. It is to <see cref="Span2D{T}"/>
/// what <see cref="Memory{T}"/> is to <see cref="Span{T}"/>. For further details on how the internal layout
/// is structured, see the docs for <see cref="Span2D{T}"/>. The <see cref="Memory2D{T}"/> type can wrap arrays
/// of any rank, provided that a valid series of parameters for the target memory area(s) are specified.
/// </summary>
/// <typeparam name="T">The type of items in the current <see cref="Memory2D{T}"/> instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct Memory2D<T> : IEquatable<Memory2D<T>>
{
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The pitch of the specified 2D region.
/// </summary>
private readonly int pitch;
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
public Memory2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
/// </exception>
public Memory2D(T[] array, int offset, int height, int width, int pitch)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)offset > (uint)array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = array.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
public Memory2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
this.instance = array;
this.offset = GetArray2DDataByteOffset<T>();
this.height = array.GetLength(0);
this.width = array.GetLength(1);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
public Memory2D(T[,]? array, int row, int column, int height, int width)
{
if (array is null)
{
if (row != 0 || column != 0 || height != 0 || width != 0)
{
ThrowHelper.ThrowArgumentException();
}
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
int
rows = array.GetLength(0),
columns = array.GetLength(1);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public Memory2D(T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
this.width = array.GetLength(2);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArrayTypeMismatchException">
/// Thrown when <paramref name="array"/> doesn't match <typeparamref name="T"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public Memory2D(T[,,] array, int depth, int row, int column, int height, int width)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
int
rows = array.GetLength(1),
columns = array.GetLength(2);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
public Memory2D(MemoryManager<T> memoryManager, int height, int width)
: this(memoryManager, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
/// </exception>
public Memory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
{
int length = memoryManager.GetSpan().Length;
if ((uint)offset > (uint)length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = memoryManager;
this.offset = (nint)(uint)offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
internal Memory2D(Memory<T> memory, int height, int width)
: this(memory, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
internal Memory2D(Memory<T> memory, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)memory.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = memory.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
// Check if the Memory<T> instance wraps a string. This is possible in case
// consumers do an unsafe cast for the entire Memory<T> object, and while not
// really safe it is still supported in CoreCLR too, so we're following suit here.
if (typeof(T) == typeof(char) &&
MemoryMarshal.TryGetString(Unsafe.As<Memory<T>, Memory<char>>(ref memory), out string? text, out int textStart, out _))
{
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
}
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
{
// Check if the input Memory<T> instance wraps an array we can access.
// This is fine, since Memory<T> on its own doesn't control the lifetime
// of the underlying array anyway, and this Memory2D<T> type would do the same.
// Using the array directly makes retrieving a Span2D<T> faster down the line,
// as we no longer have to jump through the boxed Memory<T> first anymore.
T[] array = segment.Array!;
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
}
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
{
this.instance = memoryManager;
this.offset = (nint)(uint)(memoryManagerStart + offset);
}
else
{
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
this.instance = null;
this.offset = default;
}
this.height = height;
this.width = width;
this.pitch = pitch;
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="Memory2D{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Memory2D(object instance, IntPtr offset, int height, int width, int pitch)
{
this.instance = instance;
this.offset = offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Creates a new <see cref="Memory2D{T}"/> instance from an arbitrary object.
/// </summary>
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
/// <returns>A <see cref="Memory2D{T}"/> instance with the specified parameters.</returns>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
[Pure]
public static Memory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
{
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
return new Memory2D<T>(instance, offset, height, width, pitch);
}
/// <summary>
/// Gets an empty <see cref="Memory2D{T}"/> instance.
/// </summary>
public static Memory2D<T> Empty => default;
/// <summary>
/// Gets a value indicating whether the current <see cref="Memory2D{T}"/> instance is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height == 0 || this.width == 0;
}
/// <summary>
/// Gets the length of the current <see cref="Memory2D{T}"/> instance.
/// </summary>
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)this.height * (nint)(uint)this.width;
}
/// <summary>
/// Gets the height of the underlying 2D memory area.
/// </summary>
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height;
}
/// <summary>
/// Gets the width of the underlying 2D memory area.
/// </summary>
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
/// <summary>
/// Gets a <see cref="Span2D{T}"/> instance from the current memory.
/// </summary>
public Span2D<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (this.instance is not null)
{
#if SPAN_RUNTIME_SUPPORT
if (this.instance is MemoryManager<T> memoryManager)
{
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
return new Span2D<T>(ref r1, this.height, this.width, this.pitch);
}
else
{
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return new Span2D<T>(ref r0, this.height, this.width, this.pitch);
}
#else
return new Span2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
#endif
}
return default;
}
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="rows">The target range of rows to select.</param>
/// <param name="columns">The target range of columns to select.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
/// </exception>
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
public Memory2D<T> this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var (row, height) = rows.GetOffsetAndLength(this.height);
var (column, width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="row">The target row to map within the current instance.</param>
/// <param name="column">The target column to map within the current instance.</param>
/// <param name="height">The height to map within the current instance.</param>
/// <param name="width">The width to map within the current instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for the current instance.
/// </exception>
/// <returns>A new <see cref="Memory2D{T}"/> instance representing a slice of the current one.</returns>
[Pure]
public Memory2D<T> Slice(int row, int column, int height, int width)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= this.width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (Height - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (this.width - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
int
shift = ((this.width + this.pitch) * row) + column,
pitch = this.pitch + (this.width - width);
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
return new Memory2D<T>(this.instance!, offset, height, width, pitch);
}
/// <summary>
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Copies the contents of this <see cref="Memory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="Memory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="Memory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
/// <exception cref="ArgumentException">
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
/// </exception>
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
public unsafe MemoryHandle Pin()
{
if (this.instance is not null)
{
if (this.instance is MemoryManager<T> memoryManager)
{
return memoryManager.Pin();
}
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
return new MemoryHandle(pointer, handle);
}
return default;
}
/// <summary>
/// Tries to get a <see cref="Memory{T}"/> instance, if the underlying buffer is contiguous and small enough.
/// </summary>
/// <param name="memory">The resulting <see cref="Memory{T}"/>, in case of success.</param>
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
public bool TryGetMemory(out Memory<T> memory)
{
if (this.pitch == 0 &&
Length <= int.MaxValue)
{
// Empty Memory2D<T> instance
if (this.instance is null)
{
memory = default;
}
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
{
string text = Unsafe.As<string>(this.instance)!;
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
// The string type could still be present if a user ends up creating a
// Memory2D<T> instance from a string using DangerousCreate. Similarly to
// how CoreCLR handles the equivalent case in Memory<T>, here we just do
// the necessary steps to still retrieve a Memory<T> instance correctly
// wrapping the target string. In this case, it is up to the caller
// to make sure not to ever actually write to the resulting Memory<T>.
memory = MemoryMarshal.AsMemory<T>(Unsafe.As<ReadOnlyMemory<char>, Memory<T>>(ref temp));
}
else if (this.instance is MemoryManager<T> memoryManager)
{
// If the object is a MemoryManager<T>, just slice it as needed
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
}
else if (this.instance.GetType() == typeof(T[]))
{
// If it's a T[] array, also handle the initial offset
T[] array = Unsafe.As<T[]>(this.instance)!;
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
memory = array.AsMemory(index, this.height * this.width);
}
#if SPAN_RUNTIME_SUPPORT
else if (this.instance.GetType() == typeof(T[,]) ||
this.instance.GetType() == typeof(T[,,]))
{
// If the object is a 2D or 3D array, we can create a Memory<T> from the RawObjectMemoryManager<T> type.
// We just need to use the precomputed offset pointing to the first item in the current instance,
// and the current usable length. We don't need to retrieve the current index, as the manager just offsets.
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
}
#endif
else
{
// Reuse a single failure path to reduce
// the number of returns in the method
goto Failure;
}
return true;
}
Failure:
memory = default;
return false;
}
/// <summary>
/// Copies the contents of the current <see cref="Memory2D{T}"/> instance into a new 2D array.
/// </summary>
/// <returns>A 2D array containing the data in the current <see cref="Memory2D{T}"/> instance.</returns>
[Pure]
public T[,] ToArray() => Span.ToArray();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
{
if (obj is Memory2D<T> memory)
{
return Equals(memory);
}
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
{
return readOnlyMemory.Equals(this);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Memory2D<T> other)
{
return
this.instance == other.instance &&
this.offset == other.offset &&
this.height == other.height &&
this.width == other.width &&
this.pitch == other.pitch;
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
if (this.instance is not null)
{
#if !NETSTANDARD1_4
return HashCode.Combine(
RuntimeHelpers.GetHashCode(this.instance),
this.offset,
this.height,
this.width,
this.pitch);
#else
Span<int> values = stackalloc int[]
{
RuntimeHelpers.GetHashCode(this.instance),
this.offset.GetHashCode(),
this.height,
this.width,
this.pitch
};
return values.GetDjb2HashCode();
#endif
}
return 0;
}
/// <inheritdoc/>
public override string ToString()
{
return $"Microsoft.Toolkit.HighPerformance.Memory.Memory2D<{typeof(T)}>[{this.height}, {this.width}]";
}
/// <summary>
/// Defines an implicit conversion of an array to a <see cref="Memory2D{T}"/>
/// </summary>
public static implicit operator Memory2D<T>(T[,]? array) => new Memory2D<T>(array);
}
}

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

@ -0,0 +1,923 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Buffers.Internals;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
using Microsoft.Toolkit.HighPerformance.Memory.Views;
using static Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#pragma warning disable CA2231
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <summary>
/// A readonly version of <see cref="Memory2D{T}"/>.
/// </summary>
/// <typeparam name="T">The type of items in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
{
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The pitch of the specified 2D region.
/// </summary>
private readonly int pitch;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="text"/>.</remarks>
public ReadOnlyMemory2D(string text, int height, int width)
: this(text, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="text"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="text"/>.
/// </exception>
public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)text.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = text.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref text.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
public ReadOnlyMemory2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = array.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
public ReadOnlyMemory2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
this.instance = array;
this.offset = GetArray2DDataByteOffset<T>();
this.height = array.GetLength(0);
this.width = array.GetLength(1);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(T[,]? array, int row, int column, int height, int width)
{
if (array is null)
{
if (row != 0 || column != 0 || height != 0 || width != 0)
{
ThrowHelper.ThrowArgumentException();
}
this = default;
return;
}
int
rows = array.GetLength(0),
columns = array.GetLength(1);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth)
{
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
this.width = array.GetLength(2);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width)
{
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
int
rows = array.GetLength(1),
columns = array.GetLength(2);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width)
: this(memoryManager, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
/// </exception>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
{
int length = memoryManager.GetSpan().Length;
if ((uint)offset > (uint)length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = memoryManager;
this.offset = (nint)(uint)offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int height, int width)
: this(memory, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="ReadOnlyMemory{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)memory.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int
area = OverflowHelper.ComputeInt32Area(height, width, pitch),
remaining = memory.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
// Check the supported underlying objects, like in Memory2D<T>
if (typeof(T) == typeof(char) &&
MemoryMarshal.TryGetString(Unsafe.As<ReadOnlyMemory<T>, ReadOnlyMemory<char>>(ref memory), out string? text, out int textStart, out _))
{
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
this.instance = text;
this.offset = text.DangerousGetObjectDataByteOffset(ref r0);
}
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
{
T[] array = segment.Array!;
this.instance = array;
this.offset = array.DangerousGetObjectDataByteOffset(ref array.DangerousGetReferenceAt(segment.Offset + offset));
}
else if (MemoryMarshal.TryGetMemoryManager<T, MemoryManager<T>>(memory, out var memoryManager, out int memoryManagerStart, out _))
{
this.instance = memoryManager;
this.offset = (nint)(uint)(memoryManagerStart + offset);
}
else
{
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
this.instance = null;
this.offset = default;
}
this.height = height;
this.width = width;
this.pitch = pitch;
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch)
{
this.instance = instance;
this.offset = offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Creates a new <see cref="ReadOnlyMemory2D{T}"/> instance from an arbitrary object.
/// </summary>
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
/// <returns>A <see cref="ReadOnlyMemory2D{T}"/> instance with the specified parameters.</returns>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsability of the caller to ensure it's valid.</remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
[Pure]
public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
{
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
IntPtr offset = instance.DangerousGetObjectDataByteOffset(ref value);
return new ReadOnlyMemory2D<T>(instance, offset, height, width, pitch);
}
/// <summary>
/// Gets an empty <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public static ReadOnlyMemory2D<T> Empty => default;
/// <summary>
/// Gets a value indicating whether the current <see cref="ReadOnlyMemory2D{T}"/> instance is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height == 0 || this.width == 0;
}
/// <summary>
/// Gets the length of the current <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)this.height * (nint)(uint)this.width;
}
/// <summary>
/// Gets the height of the underlying 2D memory area.
/// </summary>
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height;
}
/// <summary>
/// Gets the width of the underlying 2D memory area.
/// </summary>
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
/// <summary>
/// Gets a <see cref="ReadOnlySpan2D{T}"/> instance from the current memory.
/// </summary>
public ReadOnlySpan2D<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (this.instance is not null)
{
#if SPAN_RUNTIME_SUPPORT
if (this.instance is MemoryManager<T> memoryManager)
{
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
return new ReadOnlySpan2D<T>(r1, this.height, this.width, this.pitch);
}
else
{
// This handles both arrays and strings
ref T r0 = ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset);
return new ReadOnlySpan2D<T>(r0, this.height, this.width, this.pitch);
}
#else
return new ReadOnlySpan2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
#endif
}
return default;
}
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="rows">The target range of rows to select.</param>
/// <param name="columns">The target range of columns to select.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
public ReadOnlyMemory2D<T> this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
var (row, height) = rows.GetOffsetAndLength(this.height);
var (column, width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="row">The target row to map within the current instance.</param>
/// <param name="column">The target column to map within the current instance.</param>
/// <param name="height">The height to map within the current instance.</param>
/// <param name="width">The width to map within the current instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for the current instance.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
[Pure]
public ReadOnlyMemory2D<T> Slice(int row, int column, int height, int width)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= this.width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (Height - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (this.width - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
int
shift = ((this.width + this.pitch) * row) + column,
pitch = this.pitch + (this.width - width);
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
return new ReadOnlyMemory2D<T>(this.instance!, offset, height, width, pitch);
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
/// <exception cref="ArgumentException">
/// An instance with nonprimitive (non-blittable) members cannot be pinned.
/// </exception>
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
public unsafe MemoryHandle Pin()
{
if (this.instance is not null)
{
if (this.instance is MemoryManager<T> memoryManager)
{
return memoryManager.Pin();
}
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
void* pointer = Unsafe.AsPointer(ref this.instance.DangerousGetObjectDataReferenceAt<T>(this.offset));
return new MemoryHandle(pointer, handle);
}
return default;
}
/// <summary>
/// Tries to get a <see cref="ReadOnlyMemory{T}"/> instance, if the underlying buffer is contiguous and small enough.
/// </summary>
/// <param name="memory">The resulting <see cref="ReadOnlyMemory{T}"/>, in case of success.</param>
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
public bool TryGetMemory(out ReadOnlyMemory<T> memory)
{
if (this.pitch == 0 &&
Length <= int.MaxValue)
{
// Empty Memory2D<T> instance
if (this.instance is null)
{
memory = default;
}
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
{
// Here we need to create a Memory<char> from the wrapped string, and to do so we need to do an inverse
// lookup to find the initial index of the string with respect to the byte offset we're currently using,
// which refers to the raw string object data. This can include variable padding or other additional
// fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset
// difference between the start of the Span<char> (which directly wraps just the actual character data
// within the string), and the input reference, which we can get from the byte offset in use. The result
// is the character index which we can use to create the final Memory<char> instance.
string text = Unsafe.As<string>(this.instance)!;
int index = text.AsSpan().IndexOf(in text.DangerousGetObjectDataReferenceAt<char>(this.offset));
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
memory = Unsafe.As<ReadOnlyMemory<char>, ReadOnlyMemory<T>>(ref temp);
}
else if (this.instance is MemoryManager<T> memoryManager)
{
// If the object is a MemoryManager<T>, just slice it as needed
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
}
else if (this.instance.GetType() == typeof(T[]))
{
// If it's a T[] array, also handle the initial offset
T[] array = Unsafe.As<T[]>(this.instance)!;
int index = array.AsSpan().IndexOf(ref array.DangerousGetObjectDataReferenceAt<T>(this.offset));
memory = array.AsMemory(index, this.height * this.width);
}
#if SPAN_RUNTIME_SUPPORT
else if (this.instance.GetType() == typeof(T[,]) ||
this.instance.GetType() == typeof(T[,,]))
{
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
}
#endif
else
{
// Reuse a single failure path to reduce
// the number of returns in the method
goto Failure;
}
return true;
}
Failure:
memory = default;
return false;
}
/// <summary>
/// Copies the contents of the current <see cref="ReadOnlyMemory2D{T}"/> instance into a new 2D array.
/// </summary>
/// <returns>A 2D array containing the data in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
[Pure]
public T[,] ToArray() => Span.ToArray();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
{
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
{
return Equals(readOnlyMemory);
}
if (obj is Memory2D<T> memory)
{
return Equals(memory);
}
return false;
}
/// <inheritdoc/>
public bool Equals(ReadOnlyMemory2D<T> other)
{
return
this.instance == other.instance &&
this.offset == other.offset &&
this.height == other.height &&
this.width == other.width &&
this.pitch == other.pitch;
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
if (this.instance is not null)
{
#if !NETSTANDARD1_4
return HashCode.Combine(
RuntimeHelpers.GetHashCode(this.instance),
this.offset,
this.height,
this.width,
this.pitch);
#else
Span<int> values = stackalloc int[]
{
RuntimeHelpers.GetHashCode(this.instance),
this.offset.GetHashCode(),
this.height,
this.width,
this.pitch
};
return values.GetDjb2HashCode();
#endif
}
return 0;
}
/// <inheritdoc/>
public override string ToString()
{
return $"Microsoft.Toolkit.HighPerformance.Memory.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]";
}
/// <summary>
/// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new ReadOnlyMemory2D<T>(array);
/// <summary>
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory) => Unsafe.As<Memory2D<T>, ReadOnlyMemory2D<T>>(ref memory);
}
}

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

@ -0,0 +1,201 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <inheritdoc cref="ReadOnlySpan2D{T}"/>
public readonly ref partial struct ReadOnlySpan2D<T>
{
/// <summary>
/// Gets an enumerable that traverses items in a specified row.
/// </summary>
/// <param name="row">The target row to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRefEnumerable<T> GetRow(int row)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
nint startIndex = (nint)(uint)this.stride * (nint)(uint)row;
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Width, 1);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, this.width, 1);
#endif
}
/// <summary>
/// Gets an enumerable that traverses items in a specified column.
/// </summary>
/// <param name="column">The target column to enumerate within the current <see cref="ReadOnlySpan2D{T}"/> instance.</param>
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="ReadOnlyRefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyRefEnumerable<T> GetColumn(int column)
{
if ((uint)column >= Width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Height, this.stride);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
return new ReadOnlyRefEnumerable<T>(this.instance!, offset, Height, this.stride);
#endif
}
/// <summary>
/// Returns an enumerator for the current <see cref="ReadOnlySpan2D{T}"/> instance.
/// </summary>
/// <returns>
/// An enumerator that can be used to traverse the items in the current <see cref="ReadOnlySpan2D{T}"/> instance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="ReadOnlySpan{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>Just like in <see cref="ReadOnlySpan2D{T}"/>, the length is the height of the 2D region.</remarks>
private readonly ReadOnlySpan<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
#endif
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The stride of the specified 2D region.
/// </summary>
private readonly int stride;
/// <summary>
/// The current horizontal offset.
/// </summary>
private int x;
/// <summary>
/// The current vertical offset.
/// </summary>
private int y;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The target <see cref="ReadOnlySpan2D{T}"/> instance to enumerate.</param>
internal Enumerator(ReadOnlySpan2D<T> span)
{
#if SPAN_RUNTIME_SUPPORT
this.span = span.span;
#else
this.instance = span.instance;
this.offset = span.offset;
this.height = span.height;
#endif
this.width = span.width;
this.stride = span.stride;
this.x = -1;
this.y = 0;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int x = this.x + 1;
// Horizontal move, within range
if (x < this.width)
{
this.x = x;
return true;
}
// We reached the end of a row and there is at least
// another row available: wrap to a new line and continue.
this.x = 0;
#if SPAN_RUNTIME_SUPPORT
return ++this.y < this.span.Length;
#else
return ++this.y < this.height;
#endif
}
/// <summary>
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
/// </summary>
public readonly ref readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
return ref Unsafe.Add(ref r0, index);
}
}
}
}
}

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

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

@ -0,0 +1,201 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#if SPAN_RUNTIME_SUPPORT
using System.Runtime.InteropServices;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
namespace Microsoft.Toolkit.HighPerformance.Memory
{
/// <inheritdoc cref="Span2D{T}"/>
public readonly ref partial struct Span2D<T>
{
/// <summary>
/// Gets an enumerable that traverses items in a specified row.
/// </summary>
/// <param name="row">The target row to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefEnumerable<T> GetRow(int row)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
nint startIndex = (nint)(uint)this.Stride * (nint)(uint)row;
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
#if SPAN_RUNTIME_SUPPORT
return new RefEnumerable<T>(ref r1, Width, 1);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
return new RefEnumerable<T>(this.Instance, offset, this.width, 1);
#endif
}
/// <summary>
/// Gets an enumerable that traverses items in a specified column.
/// </summary>
/// <param name="column">The target column to enumerate within the current <see cref="Span2D{T}"/> instance.</param>
/// <returns>A <see cref="RefEnumerable{T}"/> with target items to enumerate.</returns>
/// <remarks>The returned <see cref="RefEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RefEnumerable<T> GetColumn(int column)
{
if ((uint)column >= Width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
ref T r0 = ref DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
#if SPAN_RUNTIME_SUPPORT
return new RefEnumerable<T>(ref r1, Height, this.Stride);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.Instance, ref r1);
return new RefEnumerable<T>(this.Instance, offset, Height, this.Stride);
#endif
}
/// <summary>
/// Returns an enumerator for the current <see cref="Span2D{T}"/> instance.
/// </summary>
/// <returns>
/// An enumerator that can be used to traverse the items in the current <see cref="Span2D{T}"/> instance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="Span2D{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
#if SPAN_RUNTIME_SUPPORT
/// <summary>
/// The <see cref="Span{T}"/> instance pointing to the first item in the target memory area.
/// </summary>
/// <remarks>Just like in <see cref="Span2D{T}"/>, the length is the height of the 2D region.</remarks>
private readonly Span<T> span;
#else
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
#endif
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The stride of the specified 2D region.
/// </summary>
private readonly int stride;
/// <summary>
/// The current horizontal offset.
/// </summary>
private int x;
/// <summary>
/// The current vertical offset.
/// </summary>
private int y;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The target <see cref="Span2D{T}"/> instance to enumerate.</param>
internal Enumerator(Span2D<T> span)
{
#if SPAN_RUNTIME_SUPPORT
this.span = span.span;
#else
this.instance = span.Instance;
this.offset = span.Offset;
this.height = span.height;
#endif
this.width = span.width;
this.stride = span.Stride;
this.x = -1;
this.y = 0;
}
/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int x = this.x + 1;
// Horizontal move, within range
if (x < this.width)
{
this.x = x;
return true;
}
// We reached the end of a row and there is at least
// another row available: wrap to a new line and continue.
this.x = 0;
#if SPAN_RUNTIME_SUPPORT
return ++this.y < this.span.Length;
#else
return ++this.y < this.height;
#endif
}
/// <summary>
/// Gets the duck-typed <see cref="System.Collections.Generic.IEnumerator{T}.Current"/> property.
/// </summary>
public readonly ref T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref MemoryMarshal.GetReference(this.span);
#else
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
#endif
nint index = ((nint)(uint)this.y * (nint)(uint)this.stride) + (nint)(uint)this.x;
return ref Unsafe.Add(ref r0, index);
}
}
}
}
}

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

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

@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
namespace Microsoft.Toolkit.HighPerformance.Memory.Views
{
/// <summary>
/// A debug proxy used to display items in a 2D layout.
/// </summary>
/// <typeparam name="T">The type of items to display.</typeparam>
internal sealed class MemoryDebugView2D<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memory">The input <see cref="Memory2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(Memory2D<T> memory)
{
this.Items = memory.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="memory">The input <see cref="ReadOnlyMemory2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(ReadOnlyMemory2D<T> memory)
{
this.Items = memory.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="span">The input <see cref="Span2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(Span2D<T> span)
{
this.Items = span.ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="MemoryDebugView2D{T}"/> class with the specified parameters.
/// </summary>
/// <param name="span">The input <see cref="ReadOnlySpan2D{T}"/> instance with the items to display.</param>
public MemoryDebugView2D(ReadOnlySpan2D<T> span)
{
this.Items = span.ToArray();
}
/// <summary>
/// Gets the items to display for the current instance
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public T[,]? Items { get; }
}
}

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

@ -1,19 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1;net5.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Windows Community Toolkit High Performance .NET Standard</Title>
<Description>
This package includes high performance .NET Standard helpers such as:
- Memory2D&lt;T&gt; and Span2D&lt;T&gt;: two types providing fast and allocation-free abstraction over 2D memory areas.
- ArrayPoolBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt; implementation using pooled arrays, which also supports IMemoryOwner&lt;T&gt;.
- MemoryBufferWriter&lt;T&gt;: an IBufferWriter&lt;T&gt;: implementation that can wrap external Memory&lt;T&gt;: instances.
- MemoryOwner&lt;T&gt;: an IMemoryOwner&lt;T&gt; implementation with an embedded length and a fast Span&lt;T&gt; accessor.
- SpanOwner&lt;T&gt;: a stack-only type with the ability to rent a buffer of a specified length and getting a Span&lt;T&gt; from it.
- StringPool: a configurable pool for string instances that be used to minimize allocations when creating multiple strings from char buffers.
- String, array, Span&lt;T&gt;, Memory&lt;T&gt; extensions and more, all focused on high performance.
- String, array, Memory&lt;T&gt;, Span&lt;T&gt; extensions and more, all focused on high performance.
- HashCode&lt;T&gt;: a SIMD-enabled extension of HashCode to quickly process sequences of values.
- BitHelper: a class with helper methods to perform bit operations on numeric types.
- ParallelHelper: helpers to work with parallel code in a highly optimized manner.
@ -40,7 +41,7 @@
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
@ -50,14 +51,14 @@
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<ItemGroup>
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
<PropertyGroup>
@ -74,21 +75,30 @@
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT</DefineConstants>
</PropertyGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'net5.0' ">
<PropertyGroup>
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT</DefineConstants>
</PropertyGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
<PropertyGroup>
<!-- NETCORE_RUNTIME: to avoid issues with APIs that assume a specific memory layout, we define a
.NET Core runtime constant to indicate the either .NET Core 2.1 or .NET Core 3.1. These are
.NET Core runtime constant to indicate either .NET Core 2.1 or .NET Core 3.1. These are
runtimes with the same overall memory layout for objects (in particular: strings, SZ arrays,
and 2D arrays). We can use this constant to make sure that APIs that are exclusively available
for .NET Standard targets do not make any assumption of any internals of the runtime being
actually used by the consumers. -->
and ND arrays). We can use this constant to make sure that APIs that are exclusively available
for .NET Standard targets do not make any assumtpion of any internals of the runtime being
actually used by the consumers. .NET 5.0 would fall into this category as well, but we don't
need to include that target as it offers APIs that don't require runtime-based workarounds.-->
<DefineConstants>NETSTANDARD2_1_OR_GREATER;SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>
</PropertyGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
<PropertyGroup>
<DefineConstants>SPAN_RUNTIME_SUPPORT;NETCORE_RUNTIME</DefineConstants>

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

@ -55,7 +55,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// <summary>
/// Gets a value indicating whether or not the current <see cref="NullableReadOnlyRef{T}"/> instance wraps a valid reference that can be accessed.
/// </summary>
public bool HasValue
public unsafe bool HasValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@ -63,7 +63,7 @@ namespace Microsoft.Toolkit.HighPerformance
// See comment in NullableRef<T> about this
byte length = unchecked((byte)this.span.Length);
return Unsafe.As<byte, bool>(ref length);
return *(bool*)&length;
}
}

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

@ -53,7 +53,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// <summary>
/// Gets a value indicating whether or not the current <see cref="NullableRef{T}"/> instance wraps a valid reference that can be accessed.
/// </summary>
public bool HasValue
public unsafe bool HasValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@ -67,7 +67,7 @@ namespace Microsoft.Toolkit.HighPerformance
// This results in a single movzx instruction on x86-64.
byte length = unchecked((byte)Span.Length);
return Unsafe.As<byte, bool>(ref length);
return *(bool*)&length;
}
}

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

@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.CompilerServices;
// We can suppress the .init flag for local variables for the entire module.
// This doesn't affect the correctness of methods in this assembly, as none of them
// are relying on the JIT ensuring that all local memory is zeroed out to work. Doing
// this can provide some minor performance benefits, depending on the workload.
[module: SkipLocalsInit]

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

@ -36,6 +36,16 @@ namespace Microsoft.Toolkit.HighPerformance
Span = MemoryMarshal.CreateReadOnlySpan(ref r0, 1);
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRef{T}"/> struct.
/// </summary>
/// <param name="pointer">The pointer to the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ReadOnlyRef(void* pointer)
: this(Unsafe.AsRef<T>(pointer))
{
}
/// <summary>
/// Gets the readonly <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>

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

@ -34,6 +34,16 @@ namespace Microsoft.Toolkit.HighPerformance
Span = MemoryMarshal.CreateSpan(ref value, 1);
}
/// <summary>
/// Initializes a new instance of the <see cref="Ref{T}"/> struct.
/// </summary>
/// <param name="pointer">The pointer to the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe Ref(void* pointer)
: this(ref Unsafe.AsRef<T>(pointer))
{
}
/// <summary>
/// Gets the <typeparamref name="T"/> reference represented by the current <see cref="Ref{T}"/> instance.
/// </summary>

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

@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if SPAN_RUNTIME_SUPPORT
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <inheritdoc cref="IBufferWriterStream{TWriter}"/>
internal sealed partial class IBufferWriterStream<TWriter>
{
/// <inheritdoc/>
public override void CopyTo(Stream destination, int bufferSize)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask(Task.FromCanceled(cancellationToken));
}
try
{
Write(buffer.Span);
return default;
}
catch (OperationCanceledException e)
{
return new ValueTask(Task.FromCanceled(e.CancellationToken));
}
catch (Exception e)
{
return new ValueTask(Task.FromException(e));
}
}
/// <inheritdoc/>
public override int Read(Span<byte> buffer)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override void Write(ReadOnlySpan<byte> buffer)
{
MemoryStream.ValidateDisposed(this.disposed);
Span<byte> destination = this.bufferWriter.GetSpan(buffer.Length);
if (!buffer.TryCopyTo(destination))
{
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.bufferWriter.Advance(buffer.Length);
}
}
}
#endif

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

@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping an <see cref="IBufferWriter{T}"/> instance.
/// </summary>
/// <typeparam name="TWriter">The type of buffer writer to use.</typeparam>
internal sealed partial class IBufferWriterStream<TWriter> : Stream
where TWriter : struct, IBufferWriter<byte>
{
/// <summary>
/// The target <typeparamref name="TWriter"/> instance to use.
/// </summary>
private readonly TWriter bufferWriter;
/// <summary>
/// Indicates whether or not the current instance has been disposed
/// </summary>
private bool disposed;
/// <summary>
/// Initializes a new instance of the <see cref="IBufferWriterStream{TWriter}"/> class.
/// </summary>
/// <param name="bufferWriter">The target <see cref="IBufferWriter{T}"/> instance to use.</param>
public IBufferWriterStream(TWriter bufferWriter)
{
this.bufferWriter = bufferWriter;
}
/// <inheritdoc/>
public override bool CanRead => false;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !this.disposed;
}
/// <inheritdoc/>
public override long Length => throw MemoryStream.GetNotSupportedException();
/// <inheritdoc/>
public override long Position
{
get => throw MemoryStream.GetNotSupportedException();
set => throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override void Flush()
{
}
/// <inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public override Task<int> ReadAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
try
{
Write(buffer, offset, count);
return Task.CompletedTask;
}
catch (OperationCanceledException e)
{
return Task.FromCanceled(e.CancellationToken);
}
catch (Exception e)
{
return Task.FromException(e);
}
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override void SetLength(long value)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override int Read(byte[]? buffer, int offset, int count)
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override int ReadByte()
{
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>
public override void Write(byte[]? buffer, int offset, int count)
{
MemoryStream.ValidateDisposed(this.disposed);
MemoryStream.ValidateBuffer(buffer, offset, count);
Span<byte>
source = buffer.AsSpan(offset, count),
destination = this.bufferWriter.GetSpan(count);
if (!source.TryCopyTo(destination))
{
MemoryStream.ThrowArgumentExceptionForEndOfStreamOnWrite();
}
this.bufferWriter.Advance(count);
}
/// <inheritdoc/>
public override void WriteByte(byte value)
{
MemoryStream.ValidateDisposed(this.disposed);
this.bufferWriter.GetSpan(1)[0] = value;
this.bufferWriter.Advance(1);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
this.disposed = true;
}
}
}

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

@ -12,6 +12,23 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// </summary>
internal static partial class MemoryStream
{
/// <summary>
/// Gets a standard <see cref="NotSupportedException"/> instance for a stream.
/// </summary>
/// <returns>A <see cref="NotSupportedException"/> with the standard text.</returns>
public static Exception GetNotSupportedException()
{
return new NotSupportedException("The requested operation is not supported for this stream.");
}
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to perform a not supported operation.
/// </summary>
public static void ThrowNotSupportedException()
{
throw GetNotSupportedException();
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
/// </summary>
@ -20,14 +37,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
throw new ArgumentException("The current stream can't contain the requested input data.");
}
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
/// </summary>
public static void ThrowNotSupportedExceptionForSetLength()
{
throw new NotSupportedException("Setting the length is not supported for this stream.");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
/// </summary>
@ -77,14 +86,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer");
}
/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to write on a readonly stream.
/// </summary>
private static void ThrowNotSupportedExceptionForCanWrite()
{
throw new NotSupportedException("The current stream doesn't support writing.");
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
/// </summary>

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

@ -13,14 +13,14 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
internal static partial class MemoryStream
{
/// <summary>
/// Validates the <see cref="Stream.Position"/> argument.
/// Validates the <see cref="Stream.Position"/> argument (it needs to be in the [0, length]) range.
/// </summary>
/// <param name="position">The new <see cref="Stream.Position"/> value being set.</param>
/// <param name="length">The maximum length of the target <see cref="Stream"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidatePosition(long position, int length)
{
if ((ulong)position >= (ulong)length)
if ((ulong)position > (ulong)length)
{
ThrowArgumentOutOfRangeExceptionForPosition();
}
@ -64,7 +64,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
{
if (!canWrite)
{
ThrowNotSupportedExceptionForCanWrite();
ThrowNotSupportedException();
}
}

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

@ -220,7 +220,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <inheritdoc/>
public sealed override void SetLength(long value)
{
MemoryStream.ThrowNotSupportedExceptionForSetLength();
throw MemoryStream.GetNotSupportedException();
}
/// <inheritdoc/>

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

@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
namespace Microsoft.Toolkit.HighPerformance.Streams.Sources
{
/// <summary>
/// An <see cref="IBufferWriter{T}"/> implementation wrapping an <see cref="ArrayPoolBufferWriter{T}"/> instance.
/// </summary>
internal readonly struct ArrayBufferWriterOwner : IBufferWriter<byte>
{
/// <summary>
/// The wrapped <see cref="ArrayPoolBufferWriter{T}"/> array.
/// </summary>
private readonly ArrayPoolBufferWriter<byte> writer;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayBufferWriterOwner"/> struct.
/// </summary>
/// <param name="writer">The wrapped <see cref="ArrayPoolBufferWriter{T}"/> instance.</param>
public ArrayBufferWriterOwner(ArrayPoolBufferWriter<byte> writer)
{
this.writer = writer;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
this.writer.Advance(count);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<byte> GetMemory(int sizeHint = 0)
{
return this.writer.GetMemory(sizeHint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetSpan(int sizeHint = 0)
{
return this.writer.GetSpan(sizeHint);
}
}
}

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

@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Buffers;
namespace Microsoft.Toolkit.HighPerformance.Streams.Sources
{
/// <summary>
/// An <see cref="IBufferWriter{T}"/> implementation wrapping an <see cref="IBufferWriter{T}"/> instance.
/// </summary>
internal readonly struct IBufferWriterOwner : IBufferWriter<byte>
{
/// <summary>
/// The wrapped <see cref="ArrayPoolBufferWriter{T}"/> array.
/// </summary>
private readonly IBufferWriter<byte> writer;
/// <summary>
/// Initializes a new instance of the <see cref="IBufferWriterOwner"/> struct.
/// </summary>
/// <param name="writer">The wrapped <see cref="IBufferWriter{T}"/> instance.</param>
public IBufferWriterOwner(IBufferWriter<byte> writer)
{
this.writer = writer;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
this.writer.Advance(count);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<byte> GetMemory(int sizeHint = 0)
{
return this.writer.GetMemory(sizeHint);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetSpan(int sizeHint = 0)
{
return this.writer.GetSpan(sizeHint);
}
}
}

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

@ -27,25 +27,39 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
public event PropertyChangingEventHandler? PropertyChanging;
/// <summary>
/// Performs the required configuration when a property has changed, and then
/// raises the <see cref="PropertyChanged"/> event to notify listeners of the update.
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <remarks>The base implementation only raises the <see cref="PropertyChanged"/> event.</remarks>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
/// <param name="e">The input <see cref="PropertyChangedEventArgs"/> instance.</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
PropertyChanged?.Invoke(this, e);
}
/// <summary>
/// Performs the required configuration when a property is changing, and then
/// raises the <see cref="PropertyChanged"/> event to notify listeners of the update.
/// Raises the <see cref="PropertyChanging"/> event.
/// </summary>
/// <param name="e">The input <see cref="PropertyChangingEventArgs"/> instance.</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
{
PropertyChanging?.Invoke(this, e);
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <remarks>The base implementation only raises the <see cref="PropertyChanging"/> event.</remarks>
protected virtual void OnPropertyChanging([CallerMemberName] string? propertyName = null)
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Raises the <see cref="PropertyChanging"/> event.
/// </summary>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
}
/// <summary>

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

@ -19,33 +19,28 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </summary>
public abstract class ObservableValidator : ObservableObject, INotifyDataErrorInfo
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="HasErrors"/>.
/// </summary>
private static readonly PropertyChangedEventArgs HasErrorsChangedEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
/// <summary>
/// The <see cref="Dictionary{TKey,TValue}"/> instance used to store previous validation results.
/// </summary>
private readonly Dictionary<string, List<ValidationResult>> errors = new Dictionary<string, List<ValidationResult>>();
/// <summary>
/// Indicates the total number of properties with errors (not total errors).
/// This is used to allow <see cref="HasErrors"/> to operate in O(1) time, as it can just
/// check whether this value is not 0 instead of having to traverse <see cref="errors"/>.
/// </summary>
private int totalErrors;
/// <inheritdoc/>
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
/// <inheritdoc/>
public bool HasErrors
{
get
{
// This uses the value enumerator for Dictionary<TKey, TValue>.ValueCollection, so it doesn't
// allocate. Accessing this property is O(n), but we can stop as soon as we find at least one
// error in the whole entity, and doing this saves 8 bytes in the object size (no fields needed).
foreach (var value in this.errors.Values)
{
if (value.Count > 0)
{
return true;
}
}
return false;
}
}
public bool HasErrors => this.totalErrors > 0;
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
@ -67,12 +62,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(ref T field, T newValue, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(ref field, newValue, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, propertyName);
return propertyChanged;
}
/// <summary>
@ -90,12 +87,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(ref field, newValue, comparer, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, comparer, propertyName);
return propertyChanged;
}
/// <summary>
@ -120,12 +119,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -144,12 +145,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, comparer, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -172,12 +175,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
where TModel : class
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, model, callback, propertyName);
return propertyChanged;
}
/// <summary>
@ -202,12 +207,123 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
where TModel : class
{
if (validate)
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
if (propertyChanged && validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
return propertyChanged;
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(ref T,T,string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(ref T field, T newValue, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(ref field, newValue, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(ref T,T,IEqualityComparer{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(ref field, newValue, comparer, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(T oldValue, T newValue, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{T}(T,T,IEqualityComparer{T},Action{T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, comparer, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
where TModel : class
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, model, callback, propertyName);
}
/// <summary>
/// Tries to validate a new value for a specified property. If the validation is successful,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string?)"/> is called, otherwise no state change is performed.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns>Whether the validation was successful and the property value changed as well.</returns>
protected bool TrySetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, out IReadOnlyCollection<ValidationResult> errors, [CallerMemberName] string? propertyName = null)
where TModel : class
{
return TryValidateProperty(newValue, propertyName, out errors) &&
SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
}
/// <inheritdoc/>
@ -285,6 +401,34 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
new ValidationContext(this, null, null) { MemberName = propertyName },
propertyErrors);
// Update the shared counter for the number of errors, and raise the
// property changed event if necessary. We decrement the number of total
// errors if the current property is valid but it wasn't so before this
// validation, and we increment it if the validation failed after being
// correct before. The property changed event is raised whenever the
// number of total errors is either decremented to 0, or incremented to 1.
if (isValid)
{
if (errorsChanged)
{
this.totalErrors--;
if (this.totalErrors == 0)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
}
}
else if (!errorsChanged)
{
this.totalErrors++;
if (this.totalErrors == 1)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
}
// Only raise the event once if needed. This happens either when the target property
// had existing errors and is now valid, or if the validation has failed and there are
// new errors to broadcast, regardless of the previous validation state for the property.
@ -294,6 +438,61 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
}
}
/// <summary>
/// Tries to validate a property with a specified name and a given input value, and returns
/// the computed errors, if any. If the property is valid, it is assumed that its value is
/// about to be set in the current object. Otherwise, no observable local state is modified.
/// </summary>
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <param name="errors">The resulting validation errors, if any.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
private bool TryValidateProperty(object? value, string? propertyName, out IReadOnlyCollection<ValidationResult> errors)
{
if (propertyName is null)
{
ThrowArgumentNullExceptionForNullPropertyName();
}
// Add the cached errors list for later use.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
this.errors.Add(propertyName!, propertyErrors);
}
bool hasErrors = propertyErrors.Count > 0;
List<ValidationResult> localErrors = new List<ValidationResult>();
// Validate the property, by adding new errors to the local list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
localErrors);
// We only modify the state if the property is valid and it wasn't so before. In this case, we
// clear the cached list of errors (which is visible to consumers) and raise the necessary events.
if (isValid && hasErrors)
{
propertyErrors.Clear();
this.totalErrors--;
if (this.totalErrors == 0)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
errors = localErrors;
return isValid;
}
#pragma warning disable SA1204
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when a property name given as input is <see langword="null"/>.

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

@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
#nullable enable
namespace Microsoft.Toolkit.Mvvm.DependencyInjection
{
/// <summary>
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type.
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe
/// service provider instance, which can then be used to resolve service instances.
/// The first step to use this feature is to declare some services, for instance:
/// <code>
/// public interface ILogger
/// {
/// void Log(string text);
/// }
/// </code>
/// <code>
/// public class ConsoleLogger : ILogger
/// {
/// void Log(string text) => Console.WriteLine(text);
/// }
/// </code>
/// Then the services configuration should then be done at startup, by calling the <see cref="ConfigureServices"/>
/// method and passing an <see cref="IServiceProvider"/> instance with the services to use. That instance can
/// be from any library offering dependency injection functionality, such as Microsoft.Extensions.DependencyInjection.
/// For instance, using that library, <see cref="ConfigureServices"/> can be used as follows in this example:
/// <code>
/// Ioc.Default.ConfigureServices(
/// new ServiceCollection()
/// .AddSingleton&lt;ILogger, Logger&gt;()
/// .BuildServiceProvider());
/// </code>
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>)
/// to retrieve the service instances from anywhere in your application, by doing as follows:
/// <code>
/// Ioc.Default.GetService&lt;ILogger&gt;().Log("Hello world!");
/// </code>
/// </summary>
public sealed class Ioc : IServiceProvider
{
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();
/// <summary>
/// The <see cref="IServiceProvider"/> instance to use, if initialized.
/// </summary>
private volatile IServiceProvider? serviceProvider;
/// <inheritdoc/>
public object? GetService(Type serviceType)
{
// As per section I.12.6.6 of the official CLI ECMA-335 spec:
// "[...] read and write access to properly aligned memory locations no larger than the native
// word size is atomic when all the write accesses to a location are the same size. Atomic writes
// shall alter no bits other than those written. Unless explicit layout control is used [...],
// data elements no larger than the natural word size [...] shall be properly aligned.
// Object references shall be treated as though they are stored in the native word size."
// The field being accessed here is of native int size (reference type), and is only ever accessed
// directly and atomically by a compare exchange instruction (see below), or here. We can therefore
// assume this read is thread safe with respect to accesses to this property or to invocations to one
// of the available configuration methods. So we can just read the field directly and make the necessary
// check with our local copy, without the need of paying the locking overhead from this get accessor.
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return provider!.GetService(serviceType);
}
/// <summary>
/// Tries to resolve an instance of a specified service type.
/// </summary>
/// <typeparam name="T">The type of service to resolve.</typeparam>
/// <returns>An instance of the specified service, or <see langword="null"/>.</returns>
/// <exception cref="InvalidOperationException">Throw if the current <see cref="Ioc"/> instance has not been initialized.</exception>
public T? GetService<T>()
where T : class
{
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return (T?)provider!.GetService(typeof(T));
}
/// <summary>
/// Resolves an instance of a specified service type.
/// </summary>
/// <typeparam name="T">The type of service to resolve.</typeparam>
/// <returns>An instance of the specified service, or <see langword="null"/>.</returns>
/// <exception cref="InvalidOperationException">
/// Throw if the current <see cref="Ioc"/> instance has not been initialized, or if the
/// requested service type was not registered in the service provider currently in use.
/// </exception>
public T GetRequiredService<T>()
where T : class
{
IServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
T? service = (T?)provider!.GetService(typeof(T));
if (service is null)
{
ThrowInvalidOperationExceptionForUnregisteredType();
}
return service!;
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="serviceProvider">The input <see cref="IServiceProvider"/> instance to use.</param>
public void ConfigureServices(IServiceProvider serviceProvider)
{
IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null);
if (!(oldServices is null))
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="IServiceProvider"/> property is used before initialization.
/// </summary>
private static void ThrowInvalidOperationExceptionForMissingInitialization()
{
throw new InvalidOperationException("The service provider has not been configured yet");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="IServiceProvider"/> property is missing a type registration.
/// </summary>
private static void ThrowInvalidOperationExceptionForUnregisteredType()
{
throw new InvalidOperationException("The requested service type was not registered");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once.
/// </summary>
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
{
throw new InvalidOperationException("The default service provider has already been configured");
}
}
}

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

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -18,6 +19,21 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
public sealed class AsyncRelayCommand : ObservableObject, IAsyncRelayCommand
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="CanBeCanceled"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new PropertyChangedEventArgs(nameof(CanBeCanceled));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsCancellationRequested"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new PropertyChangedEventArgs(nameof(IsCancellationRequested));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsRunning"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new PropertyChangedEventArgs(nameof(IsRunning));
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
@ -91,15 +107,22 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ =>
{
OnPropertyChanged(nameof(IsRunning));
// When the task completes
OnPropertyChanged(IsRunningChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}))
{
// When setting the task
OnPropertyChanged(IsRunningChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -142,7 +165,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(cancellationTokenSource.Token);
@ -156,7 +179,8 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);
OnPropertyChanged(CanBeCanceledChangedEventArgs);
}
}
}

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

@ -91,15 +91,22 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ =>
{
OnPropertyChanged(nameof(IsRunning));
// When the task completes
OnPropertyChanged(AsyncRelayCommand.IsRunningChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}))
{
// When setting the task
OnPropertyChanged(AsyncRelayCommand.IsRunningChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -163,7 +170,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(parameter, cancellationTokenSource.Token);
@ -183,7 +190,8 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
OnPropertyChanged(AsyncRelayCommand.CanBeCanceledChangedEventArgs);
}
}
}

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

@ -27,27 +27,10 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// </summary>
private static class MethodInfos
{
/// <summary>
/// Initializes static members of the <see cref="MethodInfos"/> class.
/// </summary>
static MethodInfos()
{
RegisterIRecipient = (
from methodInfo in typeof(IMessengerExtensions).GetMethods()
where methodInfo.Name == nameof(Register) &&
methodInfo.IsGenericMethod &&
methodInfo.GetGenericArguments().Length == 2
let parameters = methodInfo.GetParameters()
where parameters.Length == 3 &&
parameters[1].ParameterType.IsGenericType &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
select methodInfo).First();
}
/// <summary>
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
/// </summary>
public static readonly MethodInfo RegisterIRecipient;
public static readonly MethodInfo RegisterIRecipient = new Action<IMessenger, IRecipient<object>, Unit>(Register).Method.GetGenericMethodDefinition();
}
/// <summary>

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

@ -65,19 +65,19 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
unchecked
{
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
// This is not a problem in this case since we already know that the base
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
int hash = RuntimeHelpers.GetHashCode(TMessage);
// To combine the two hashes, we can simply use the fast djb2 hash algorithm. Unfortunately we
// can't really skip the callvirt here (eg. by using RuntimeHelpers.GetHashCode like in other
// cases), as there are some niche cases mentioned above that might break when doing so.
// However since this method is not generally used in a hot path (eg. the message broadcasting
// only invokes this a handful of times when initially retrieving the target mapping), this
// doesn't actually make a noticeable difference despite the minor overhead of the virtual call.
int hash = TMessage.GetHashCode();
hash = (hash << 5) + hash;
hash = (hash << 5) + hash;
hash += RuntimeHelpers.GetHashCode(TToken);
hash += TToken.GetHashCode();
return hash;
}
return hash;
}
}
}

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

@ -371,8 +371,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// that doesn't expose the single standard Current property.
while (mappingEnumerator.MoveNext())
{
object recipient = mappingEnumerator.Key.Target;
// Pick the target handler, if the token is a match for the recipient
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
{
@ -382,7 +380,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// We're still using a checked span accesses here though to make sure an out of
// bounds write can never happen even if an error was present in the logic above.
pairs[2 * i] = handler!;
pairs[(2 * i) + 1] = recipient;
pairs[(2 * i) + 1] = mappingEnumerator.Key.Target;
i++;
}
}

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

@ -8,7 +8,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
@ -197,7 +197,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The advertisement.</param>
private async void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterReadLock(TimeSpan.FromSeconds(1)))
@ -281,7 +281,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
// Protect against race condition if the task runs after the app stopped the deviceWatcher.
if (sender == _deviceWatcher)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1)))
@ -331,7 +331,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
if (connectable)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1)))

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

@ -11,12 +11,11 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml.Media.Imaging;
namespace Microsoft.Toolkit.Uwp.Connectivity
@ -404,7 +403,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <exception cref="Exception">Throws Exception when no permission to access device</exception>
public async Task ConnectAsync()
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
async () =>
{
if (BluetoothLEDevice == null)
@ -478,7 +477,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <returns>The task of the update.</returns>
public async Task UpdateAsync(DeviceInformationUpdate deviceUpdate)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
DeviceInfo.Update(deviceUpdate);
@ -521,7 +520,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The arguments.</param>
private async void BluetoothLEDevice_NameChanged(BluetoothLEDevice sender, object args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() => { Name = BluetoothLEDevice.Name; }, DispatcherQueuePriority.Normal);
await DispatcherQueue.EnqueueAsync(() => { Name = BluetoothLEDevice.Name; }, DispatcherQueuePriority.Normal);
}
/// <summary>
@ -531,7 +530,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The arguments.</param>
private async void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
() =>
{
IsPaired = DeviceInfo.Pairing.IsPaired;
@ -544,7 +543,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
private async void LoadGlyph()
{
await DispatcherQueue.ExecuteOnUIThreadAsync(
await DispatcherQueue.EnqueueAsync(
async () =>
{
var deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync();

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

@ -7,7 +7,7 @@ using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Helpers;
using Microsoft.Toolkit.Uwp.Extensions;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Security.Cryptography;
@ -469,7 +469,7 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// <param name="args">The <see cref="GattValueChangedEventArgs"/> instance containing the event data.</param>
private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
await DispatcherQueue.ExecuteOnUIThreadAsync(() => { SetValue(args.CharacteristicValue); }, DispatcherQueuePriority.Normal);
await DispatcherQueue.EnqueueAsync(() => { SetValue(args.CharacteristicValue); }, DispatcherQueuePriority.Normal);
}
/// <summary>

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

@ -12,26 +12,11 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
public class NetworkHelper
{
/// <summary>
/// Private singleton field.
/// </summary>
private static NetworkHelper _instance;
/// <summary>
/// Event raised when the network changes.
/// </summary>
public event EventHandler NetworkChanged;
/// <summary>
/// Gets public singleton property.
/// </summary>
public static NetworkHelper Instance => _instance ?? (_instance = new NetworkHelper());
/// <summary>
/// Gets instance of <see cref="ConnectionInformation"/>.
/// </summary>
public ConnectionInformation ConnectionInformation { get; } = new ConnectionInformation();
/// <summary>
/// Initializes a new instance of the <see cref="NetworkHelper"/> class.
/// </summary>
@ -52,6 +37,19 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
NetworkInformation.NetworkStatusChanged -= OnNetworkStatusChanged;
}
/// <summary>
/// Gets public singleton property.
/// </summary>
public static NetworkHelper Instance { get; } = new NetworkHelper();
/// <summary>
/// Gets instance of <see cref="ConnectionInformation"/>.
/// </summary>
public ConnectionInformation ConnectionInformation { get; }
/// <summary>
/// Checks the current connection information and raises <see cref="NetworkChanged"/> if needed.
/// </summary>
private void UpdateConnectionInformation()
{
lock (ConnectionInformation)
@ -69,6 +67,9 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
}
}
/// <summary>
/// Invokes <see cref="UpdateConnectionInformation"/> when the current network status changes.
/// </summary>
private void OnNetworkStatusChanged(object sender)
{
UpdateConnectionInformation();

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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Devices.Input.Preview;
using Windows.Foundation;
@ -298,20 +299,9 @@ namespace Microsoft.Toolkit.Uwp.Input.GazeInteraction
/// </summary>
public bool IsAlwaysActivated { get; set; }
private static GazePointer _instance = null;
private static ThreadLocal<GazePointer> _instance = new ThreadLocal<GazePointer>(() => new GazePointer());
internal static GazePointer Instance
{
get
{
if (_instance == null)
{
_instance = new GazePointer();
}
return _instance;
}
}
internal static GazePointer Instance => _instance.Value;
internal void AddRoot(int proxyId)
{

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

@ -11,6 +11,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// </summary>
public sealed class AdaptiveProgressBarValue
{
/// <summary>
/// Gets or sets the property name to bind to.
/// </summary>
public string BindingName { get; set; }
/// <summary>
/// Gets or sets the value (0-1) representing the percent complete.
/// </summary>
@ -35,6 +40,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return "indeterminate";
}
if (BindingName != null)
{
return "{" + BindingName + "}";
}
return Value.ToString();
}
@ -69,5 +79,18 @@ namespace Microsoft.Toolkit.Uwp.Notifications
Value = d
};
}
/// <summary>
/// Returns a progress bar value using the specified binding name.
/// </summary>
/// <param name="bindingName">The property to bind to.</param>
/// <returns>A progress bar value.</returns>
public static AdaptiveProgressBarValue FromBinding(string bindingName)
{
return new AdaptiveProgressBarValue()
{
BindingName = bindingName
};
}
}
}

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

@ -1,7 +1,9 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>netstandard1.4;uap10.0;native;net461;netcoreapp3.1</TargetFrameworks>
<!--<TargetFrameworks>netstandard1.4;uap10.0;native;net461;netcoreapp3.1</TargetFrameworks>-->
<!-- Removed 'native' target to unblock CI on VS 16.8, tied to changes breaking workaround for https://github.com/NuGet/Home/issues/5154 -->
<TargetFrameworks>netstandard1.4;uap10.0;net461;netcoreapp3.1</TargetFrameworks>
<DefineConstants>$(DefineConstants);NETFX_CORE</DefineConstants>
<Title>Windows Community Toolkit Notifications</Title>
<Description>
@ -13,6 +15,8 @@
</Description>
<PackageTags>notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++</PackageTags>
<ExtrasImplicitPlatformPackageIsPrivate Condition=" '$(TargetFramework)' == 'native' ">true</ExtrasImplicitPlatformPackageIsPrivate>
<DefaultTargetPlatformMinVersion>10240</DefaultTargetPlatformMinVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
</PropertyGroup>
<Choose>
@ -25,23 +29,17 @@
</ItemGroup>
<PropertyGroup>
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
<DefineConstants>$(DefineConstants);WINDOWS_UWP;WIN32</DefineConstants>
</PropertyGroup>
</When>
<!--Non-desktop apps (UWP, libraries, ASP.NET servers)-->
<Otherwise>
<ItemGroup>
<!--Remove the DesktopNotificationManager code-->
<Compile Remove="DesktopNotificationManager\**\*" />
</ItemGroup>
</Otherwise>
</Choose>
<!--NET Core desktop apps also need the Registry NuGet package-->
<!--NET Core desktop apps also need the Registry NuGet package and System.Reflection.Emit for generating COM class dynamically-->
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'native' ">

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

@ -15,21 +15,21 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <summary>
/// Small Square Tile
/// </summary>
Small = 0,
Small = 1,
/// <summary>
/// Medium Square Tile
/// </summary>
Medium = 1,
Medium = 2,
/// <summary>
/// Wide Rectangle Tile
/// </summary>
Wide = 2,
Wide = 4,
/// <summary>
/// Large Square Tile
/// </summary>
Large = 4
Large = 8
}
}

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

@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
/// </summary>
/// <param name="toast">The toast to modify that will be displayed.</param>
public delegate void CustomizeToast(ToastNotification toast);
/// <summary>
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
/// </summary>
/// <param name="toast">The toast to modify that will be displayed.</param>
/// <returns>An operation.</returns>
public delegate IAsyncAction CustomizeToastAsync(ToastNotification toast);
/// <summary>
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
/// </summary>
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
public delegate void CustomizeScheduledToast(ScheduledToastNotification toast);
/// <summary>
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
/// </summary>
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
/// <returns>An operation.</returns>
public delegate IAsyncAction CustomizeScheduledToastAsync(ScheduledToastNotification toast);
}
#endif

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

@ -8,13 +8,16 @@ using System.Linq;
namespace Microsoft.Toolkit.Uwp.Notifications
{
#if !WINRT
#pragma warning disable SA1008
#pragma warning disable SA1009
/// <summary>
/// Builder class used to create <see cref="ToastContent"/>
/// </summary>
public partial class ToastContentBuilder
public
#if WINRT
sealed
#endif
partial class ToastContentBuilder
{
private IToastActions Actions
{
@ -45,6 +48,36 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
private string SerializeArgumentsIncludingGeneric(ToastArguments arguments)
{
if (_genericArguments.Count == 0)
{
return arguments.ToString();
}
foreach (var genericArg in _genericArguments)
{
if (!arguments.Contains(genericArg.Key))
{
arguments.Add(genericArg.Key, genericArg.Value);
}
}
return arguments.ToString();
}
/// <summary>
/// Add a button to the current toast.
/// </summary>
/// <param name="content">Text to display on the button.</param>
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments)
{
return AddButton(content, activationType, arguments, default);
}
/// <summary>
/// Add a button to the current toast.
/// </summary>
@ -53,7 +86,10 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
/// <param name="imageUri">Optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
#endif
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri)
{
// Add new button
ToastButton button = new ToastButton(content, arguments)
@ -61,7 +97,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
ActivationType = activationType
};
if (imageUri != default(Uri))
if (imageUri != default)
{
button.ImageUri = imageUri.OriginalString;
}
@ -76,17 +112,46 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddButton(IToastButton button)
{
if (button is ToastButton toastButton && toastButton.Content == null)
{
throw new InvalidOperationException("Content is required on button.");
}
// List has max 5 buttons
if (ButtonList.Count == 5)
{
throw new InvalidOperationException("A toast can't have more than 5 buttons");
}
if (button is ToastButton b && b.CanAddArguments())
{
foreach (var arg in _genericArguments)
{
if (!b.ContainsArgument(arg.Key))
{
b.AddArgument(arg.Key, arg.Value);
}
}
}
ButtonList.Add(button);
return this;
}
/// <summary>
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
/// </summary>
/// <param name="textBoxId">ID of an existing <see cref="ToastTextBox"/> in order to have this button display to the right of the input, achieving a quick reply scenario.</param>
/// <param name="content">Text to display on the button.</param>
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments)
{
return AddButton(textBoxId, content, activationType, arguments, default);
}
/// <summary>
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
/// </summary>
@ -96,7 +161,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
/// <param name="imageUri">An optional image icon for the button to display (required for buttons adjacent to inputs like quick reply)</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri)
{
// Add new button
ToastButton button = new ToastButton(content, arguments)
@ -105,7 +170,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
TextBoxId = textBoxId
};
if (imageUri != default(Uri))
if (imageUri != default)
{
button.ImageUri = imageUri.OriginalString;
}
@ -113,6 +178,29 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return AddButton(button);
}
#if WINRT
/// <summary>
/// Add an input text box that the user can type into.
/// </summary>
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInputTextBox(string id)
{
return AddInputTextBox(id, default, default);
}
/// <summary>
/// Add an input text box that the user can type into.
/// </summary>
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent)
{
return AddInputTextBox(id, placeHolderContent, default);
}
#endif
/// <summary>
/// Add an input text box that the user can type into.
/// </summary>
@ -120,16 +208,24 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
/// <param name="title">Title text to display above the text box.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent = default(string), string title = default(string))
public ToastContentBuilder AddInputTextBox(
string id,
#if WINRT
string placeHolderContent,
string title)
#else
string placeHolderContent = default,
string title = default)
#endif
{
var inputTextBox = new ToastTextBox(id);
if (placeHolderContent != default(string))
if (placeHolderContent != default)
{
inputTextBox.PlaceholderContent = placeHolderContent;
}
if (title != default(string))
if (title != default)
{
inputTextBox.Title = title;
}
@ -137,6 +233,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return AddToastInput(inputTextBox);
}
#if !WINRT
/// <summary>
/// Add a combo box / drop-down menu that contain options for user to select.
/// </summary>
@ -145,7 +242,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
{
return AddComboBox(id, default(string), choices);
return AddComboBox(id, default, choices);
}
/// <summary>
@ -157,7 +254,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddComboBox(string id, string defaultSelectionBoxItemId, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
{
return AddComboBox(id, default(string), defaultSelectionBoxItemId, choices);
return AddComboBox(id, default, defaultSelectionBoxItemId, choices);
}
/// <summary>
@ -185,12 +282,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
var box = new ToastSelectionBox(id);
if (defaultSelectionBoxItemId != default(string))
if (defaultSelectionBoxItemId != default)
{
box.DefaultSelectionBoxItemId = defaultSelectionBoxItemId;
}
if (title != default(string))
if (title != default)
{
box.Title = title;
}
@ -203,6 +300,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return AddToastInput(box);
}
#endif
/// <summary>
/// Add an input option to the Toast.
@ -218,5 +316,4 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
#pragma warning restore SA1008
#pragma warning restore SA1009
#endif
}

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

@ -13,7 +13,6 @@ using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.Notifications
{
#if !WINRT
/// <summary>
/// Builder class used to create <see cref="ToastContent"/>
/// </summary>
@ -81,7 +80,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
#if WINDOWS_UWP
#if !WINRT
/// <summary>
/// Create an instance of NotificationData that can be used to update toast that has a progress bar.
/// </summary>
@ -93,7 +92,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="status"> A status string, which is displayed underneath the progress bar on the left. Default to empty.</param>
/// <param name="sequence">A sequence number to prevent out-of-order updates, or assign 0 to indicate "always update".</param>
/// <returns>An instance of NotificationData that can be used to update the toast.</returns>
public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default(string), double? value = null, string valueStringOverride = default(string), string status = default(string), uint sequence = 0)
public static NotificationData CreateProgressBarData(ToastContent toast, int index = 0, string title = default, double? value = default, string valueStringOverride = default, string status = default, uint sequence = 0)
{
var progressBar = toast.Visual.BindingGeneric.Children.Where(c => c is AdaptiveProgressBar).ElementAt(index) as AdaptiveProgressBar;
if (progressBar == null)
@ -104,30 +103,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
NotificationData data = new NotificationData();
data.SequenceNumber = sequence;
if (progressBar.Title is BindableString bindableTitle && title != default(string))
// Native C++ doesn't support BindableString
if (progressBar.Title is BindableString bindableTitle && title != default)
{
data.Values[bindableTitle.BindingName] = title;
}
if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != null)
if (progressBar.Value is BindableProgressBarValue bindableProgressValue && value != default)
{
data.Values[bindableProgressValue.BindingName] = value.ToString();
}
if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default(string))
if (progressBar.ValueStringOverride is BindableString bindableValueStringOverride && valueStringOverride != default)
{
data.Values[bindableValueStringOverride.BindingName] = valueStringOverride;
}
if (progressBar.Status is BindableString bindableStatus && status != default(string))
if (progressBar.Status is BindableString bindableStatus && status != default)
{
data.Values[bindableStatus.BindingName] = status;
}
return data;
}
#endif
#endif
/// <summary>
/// Add an Attribution Text to be displayed on the toast.
/// </summary>
/// <param name="text">Text to be displayed as Attribution Text</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAttributionText(string text)
{
return AddAttributionText(text, default);
}
/// <summary>
/// Add an Attribution Text to be displayed on the toast.
@ -135,14 +145,14 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="text">Text to be displayed as Attribution Text</param>
/// <param name="language">The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR".</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAttributionText(string text, string language = default(string))
public ToastContentBuilder AddAttributionText(string text, string language)
{
AttributionText = new ToastGenericAttributionText()
{
Text = text
};
if (language != default(string))
if (language != default)
{
AttributionText.Language = language;
}
@ -150,6 +160,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return this;
}
#if WINRT
/// <summary>
/// Override the app logo with custom image of choice that will be displayed on the toast.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAppLogoOverride(Uri uri)
{
return AddAppLogoOverride(uri, default);
}
/// <summary>
/// Override the app logo with custom image of choice that will be displayed on the toast.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="hintCrop">Specify how the image should be cropped.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop)
{
return AddAppLogoOverride(uri, hintCrop, default);
}
/// <summary>
/// Override the app logo with custom image of choice that will be displayed on the toast.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="hintCrop">Specify how the image should be cropped.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop, string alternateText)
{
return AddAppLogoOverride(uri, hintCrop, alternateText, default);
}
#endif
/// <summary>
/// Override the app logo with custom image of choice that will be displayed on the toast.
/// </summary>
@ -158,24 +203,34 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAppLogoOverride(Uri uri, ToastGenericAppLogoCrop? hintCrop = null, string alternateText = default(string), bool? addImageQuery = default(bool?))
public ToastContentBuilder AddAppLogoOverride(
Uri uri,
#if WINRT
ToastGenericAppLogoCrop? hintCrop,
string alternateText,
bool? addImageQuery)
#else
ToastGenericAppLogoCrop? hintCrop = default,
string alternateText = default,
bool? addImageQuery = default)
#endif
{
AppLogoOverrideUri = new ToastGenericAppLogo()
{
Source = uri.OriginalString
};
if (hintCrop != null)
if (hintCrop != default)
{
AppLogoOverrideUri.HintCrop = hintCrop.Value;
}
if (alternateText != default(string))
if (alternateText != default)
{
AppLogoOverrideUri.AlternateText = alternateText;
}
if (addImageQuery != default(bool?))
if (addImageQuery != default)
{
AppLogoOverrideUri.AddImageQuery = addImageQuery;
}
@ -183,6 +238,29 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return this;
}
#if WINRT
/// <summary>
/// Add a hero image to the toast.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddHeroImage(Uri uri)
{
return AddHeroImage(uri, default);
}
/// <summary>
/// Add a hero image to the toast.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddHeroImage(Uri uri, string alternateText)
{
return AddHeroImage(uri, alternateText, default);
}
#endif
/// <summary>
/// Add a hero image to the toast.
/// </summary>
@ -190,19 +268,27 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddHeroImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?))
public ToastContentBuilder AddHeroImage(
Uri uri,
#if WINRT
string alternateText,
bool? addImageQuery)
#else
string alternateText = default,
bool? addImageQuery = default)
#endif
{
HeroImage = new ToastGenericHeroImage()
{
Source = uri.OriginalString
};
if (alternateText != default(string))
if (alternateText != default)
{
HeroImage.AlternateText = alternateText;
}
if (addImageQuery != default(bool?))
if (addImageQuery != default)
{
HeroImage.AddImageQuery = addImageQuery;
}
@ -210,16 +296,72 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return this;
}
#if WINRT
/// <summary>
/// Add an image inline with other toast content.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInlineImage(Uri uri)
{
return AddInlineImage(uri, default);
}
/// <summary>
/// Add an image inline with other toast content.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText)
{
return AddInlineImage(uri, alternateText, default);
}
/// <summary>
/// Add an image inline with other toast content.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
/// <param name="hintRemoveMargin">the horizontal alignment of the image.This is only supported when inside an <see cref="AdaptiveSubgroup"/>.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText = default(string), bool? addImageQuery = default(bool?), AdaptiveImageCrop? hintCrop = null, bool? hintRemoveMargin = default(bool?))
public ToastContentBuilder AddInlineImage(Uri uri, string alternateText, bool? addImageQuery)
{
return AddInlineImage(uri, alternateText, addImageQuery, default);
}
#endif
#if WINRT
/// <summary>
/// Add an image inline with other toast content.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInlineImage(
Uri uri,
string alternateText,
bool? addImageQuery,
AdaptiveImageCrop? hintCrop)
#else
/// <summary>
/// Add an image inline with other toast content.
/// </summary>
/// <param name="uri">The URI of the image. Can be from your application package, application data, or the internet. Internet images must be less than 200 KB in size.</param>
/// <param name="alternateText">A description of the image, for users of assistive technologies.</param>
/// <param name="addImageQuery">A value whether Windows is allowed to append a query string to the image URI supplied in the Tile notification.</param>
/// <param name="hintCrop">A value whether a margin is removed. images have an 8px margin around them.</param>
/// <param name="hintRemoveMargin">This property is not used. Setting this has no impact.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddInlineImage(
Uri uri,
string alternateText = default,
bool? addImageQuery = default,
AdaptiveImageCrop? hintCrop = default,
bool? hintRemoveMargin = default)
#endif
{
var inlineImage = new AdaptiveImage()
{
@ -231,24 +373,20 @@ namespace Microsoft.Toolkit.Uwp.Notifications
inlineImage.HintCrop = hintCrop.Value;
}
if (alternateText != default(string))
if (alternateText != default)
{
inlineImage.AlternateText = alternateText;
}
if (addImageQuery != default(bool?))
if (addImageQuery != default)
{
inlineImage.AddImageQuery = addImageQuery;
}
if (hintRemoveMargin != default(bool?))
{
inlineImage.HintRemoveMargin = hintRemoveMargin;
}
return AddVisualChild(inlineImage);
}
#if !WINRT
/// <summary>
/// Add a progress bar to the toast.
/// </summary>
@ -259,7 +397,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="status">A status string which is displayed underneath the progress bar. This string should reflect the status of the operation, like "Downloading..." or "Installing...". Default to empty.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-progress-bar </remarks>
public ToastContentBuilder AddProgressBar(string title = default(string), double? value = null, bool isIndeterminate = false, string valueStringOverride = default(string), string status = default(string))
public ToastContentBuilder AddProgressBar(string title = default, double? value = null, bool isIndeterminate = false, string valueStringOverride = default, string status = default)
{
int index = VisualChildren.Count(c => c is AdaptiveProgressBar);
@ -267,7 +405,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
};
if (title == default(string))
if (title == default)
{
progressBar.Title = new BindableString($"progressBarTitle_{index}");
}
@ -289,7 +427,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
progressBar.Value = value.Value;
}
if (valueStringOverride == default(string))
if (valueStringOverride == default)
{
progressBar.ValueStringOverride = new BindableString($"progressValueString_{index}");
}
@ -298,7 +436,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
progressBar.ValueStringOverride = valueStringOverride;
}
if (status == default(string))
if (status == default)
{
progressBar.Status = new BindableString($"progressStatus_{index}");
}
@ -309,16 +447,41 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return AddVisualChild(progressBar);
}
#endif
#if WINRT
/// <summary>
/// Add text to the toast.
/// </summary>
/// <param name="text">Custom text to display on the tile.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
public ToastContentBuilder AddText(string text)
{
return AddText(text, default, default);
}
/// <summary>
/// Add text to the toast.
/// </summary>
/// <param name="text">Custom text to display on the tile.</param>
/// <param name="hintStyle">Style that controls the text's font size, weight, and opacity.</param>
/// <param name="hintWrap">Indicating whether text wrapping is enabled. For Tiles, this is false by default.</param>
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display. For Tiles, this is infinity by default</param>
/// <param name="hintMinLines">The minimum number of lines the text element must display.</param>
/// <param name="hintAlign">The horizontal alignment of the text</param>
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
public ToastContentBuilder AddText(string text, int? hintMaxLines)
{
return AddText(text, hintMaxLines, default);
}
#endif
#if WINRT
/// <summary>
/// Add text to the toast.
/// </summary>
/// <param name="text">Custom text to display on the tile.</param>
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
/// <param name="language">
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
/// </param>
@ -326,7 +489,36 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
/// <exception cref="ArgumentOutOfRangeException">Throws when <paramref name="hintMaxLines"/> value is larger than 2. </exception>
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
public ToastContentBuilder AddText(string text, AdaptiveTextStyle? hintStyle = null, bool? hintWrap = default(bool?), int? hintMaxLines = default(int?), int? hintMinLines = default(int?), AdaptiveTextAlign? hintAlign = null, string language = default(string))
public ToastContentBuilder AddText(
string text,
int? hintMaxLines,
string language)
#else
/// <summary>
/// Add text to the toast.
/// </summary>
/// <param name="text">Custom text to display on the tile.</param>
/// <param name="hintStyle">This property is not used. Setting this has no effect.</param>
/// <param name="hintWrap">This property is not used. Setting this has no effect. If you need to disable wrapping, set hintMaxLines to 1.</param>
/// <param name="hintMaxLines">The maximum number of lines the text element is allowed to display.</param>
/// <param name="hintMinLines">hintMinLines is not used. Setting this has no effect.</param>
/// <param name="hintAlign">hintAlign is not used. Setting this has no effect.</param>
/// <param name="language">
/// The target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides any other specified locale, such as that in binding or visual.
/// </param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
/// <exception cref="InvalidOperationException">Throws when attempting to add/reserve more than 4 lines on a single toast. </exception>
/// <exception cref="ArgumentOutOfRangeException">Throws when <paramref name="hintMaxLines"/> value is larger than 2. </exception>
/// <remarks>More info at: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts#text-elements</remarks>
public ToastContentBuilder AddText(
string text,
AdaptiveTextStyle? hintStyle = null,
bool? hintWrap = default,
int? hintMaxLines = default,
int? hintMinLines = default,
AdaptiveTextAlign? hintAlign = null,
string language = default)
#endif
{
int lineCount = GetCurrentTextLineCount();
if (GetCurrentTextLineCount() == 4)
@ -340,22 +532,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
Text = text
};
if (hintStyle != null)
{
adaptive.HintStyle = hintStyle.Value;
}
if (hintAlign != null)
{
adaptive.HintAlign = hintAlign.Value;
}
if (hintWrap != default(bool?))
{
adaptive.HintWrap = hintWrap;
}
if (hintMaxLines != default(int?))
if (hintMaxLines != default)
{
if (hintMaxLines > 2)
{
@ -369,12 +546,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
adaptive.HintMaxLines = hintMaxLines;
}
if (hintMinLines != default(int?) && hintMinLines > 0)
{
adaptive.HintMinLines = hintMinLines;
}
if (language != default(string))
if (language != default)
{
adaptive.Language = language;
}
@ -419,6 +591,4 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return count;
}
}
#endif
}

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

@ -4,16 +4,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Toolkit.Uwp.Notifications
{
#if !WINRT
/// <summary>
/// Builder class used to create <see cref="ToastContent"/>
/// </summary>
public partial class ToastContentBuilder
#if !WINRT
: IToastActivateableBuilder<ToastContentBuilder>
#endif
{
private Dictionary<string, string> _genericArguments = new Dictionary<string, string>();
private bool _customArgumentsUsedOnToastItself;
/// <summary>
/// Gets internal instance of <see cref="ToastContent"/>. This is equivalent to the call to <see cref="ToastContentBuilder.GetToastContent"/>.
/// </summary>
@ -35,13 +40,34 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// </summary>
/// <param name="dateTime">Custom Time to be displayed on the toast</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddCustomTimeStamp(DateTime dateTime)
public ToastContentBuilder AddCustomTimeStamp(
#if WINRT
DateTimeOffset dateTime)
#else
DateTime dateTime)
#endif
{
Content.DisplayTimestamp = dateTime;
return this;
}
/// <summary>
/// Add a header to a toast.
/// </summary>
/// <param name="id">A developer-created identifier that uniquely identifies this header. If two notifications have the same header id, they will be displayed underneath the same header in Action Center.</param>
/// <param name="title">A title for the header.</param>
/// <param name="arguments">Developer-defined arguments that are returned to the app when the user clicks this header.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
/// <remarks>More info about toast header: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-headers </remarks>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
#endif
public ToastContentBuilder AddHeader(string id, string title, ToastArguments arguments)
{
return AddHeader(id, title, arguments.ToString());
}
/// <summary>
/// Add a header to a toast.
/// </summary>
@ -58,7 +84,201 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
/// <summary>
/// Add info that can be used by the application when the app was activated/launched by the toast.
/// Adds a key (without value) to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddArgument(string key)
{
return AddArgumentHelper(key, null);
}
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
#endif
public ToastContentBuilder AddArgument(string key, string value)
{
return AddArgumentHelper(key, value);
}
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
#endif
public ToastContentBuilder AddArgument(string key, int value)
{
return AddArgumentHelper(key, value.ToString());
}
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
#endif
public ToastContentBuilder AddArgument(string key, double value)
{
return AddArgumentHelper(key, value.ToString());
}
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
#endif
public ToastContentBuilder AddArgument(string key, float value)
{
return AddArgumentHelper(key, value.ToString());
}
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastContentBuilder")]
#endif
public ToastContentBuilder AddArgument(string key, bool value)
{
return AddArgumentHelper(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
}
#if !WINRT
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the toast notification or its buttons are clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself. Note that the enums are stored using their numeric value, so be aware that changing your enum number values might break existing activation of toasts currently in Action Center.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddArgument(string key, Enum value)
{
return AddArgumentHelper(key, ((int)(object)value).ToString());
}
#endif
private ToastContentBuilder AddArgumentHelper(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
bool alreadyExists = _genericArguments.ContainsKey(key);
_genericArguments[key] = value;
if (Content.ActivationType != ToastActivationType.Protocol && !_customArgumentsUsedOnToastItself)
{
Content.Launch = alreadyExists ? SerializeArgumentsHelper(_genericArguments) : AddArgumentHelper(Content.Launch, key, value);
}
if (Content.Actions is ToastActionsCustom actions)
{
foreach (var button in actions.Buttons)
{
if (button is ToastButton b && b.CanAddArguments() && !b.ContainsArgument(key))
{
b.AddArgument(key, value);
}
}
}
return this;
}
private string SerializeArgumentsHelper(IDictionary<string, string> arguments)
{
var args = new ToastArguments();
foreach (var a in arguments)
{
args.Add(a.Key, a.Value);
}
return args.ToString();
}
private string AddArgumentHelper(string existing, string key, string value)
{
string pair = ToastArguments.EncodePair(key, value);
if (existing == null)
{
return pair;
}
else
{
return existing + ToastArguments.Separator + pair;
}
}
/// <summary>
/// Configures the toast notification to launch the specified url when the toast body is clicked.
/// </summary>
/// <param name="protocol">The protocol to launch.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder SetProtocolActivation(Uri protocol)
{
return SetProtocolActivation(protocol, default);
}
/// <summary>
/// Configures the toast notification to launch the specified url when the toast body is clicked.
/// </summary>
/// <param name="protocol">The protocol to launch.</param>
/// <param name="targetApplicationPfn">New in Creators Update: The target PFN, so that regardless of whether multiple apps are registered to handle the same protocol uri, your desired app will always be launched.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder SetProtocolActivation(Uri protocol, string targetApplicationPfn)
{
Content.Launch = protocol.ToString();
Content.ActivationType = ToastActivationType.Protocol;
if (targetApplicationPfn != null)
{
if (Content.ActivationOptions == null)
{
Content.ActivationOptions = new ToastActivationOptions();
}
Content.ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
}
return this;
}
/// <summary>
/// Configures the toast notification to use background activation when the toast body is clicked.
/// </summary>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder SetBackgroundActivation()
{
Content.ActivationType = ToastActivationType.Background;
return this;
}
/// <summary>
/// Instead of this method, for foreground/background activation, it is suggested to use <see cref="AddArgument(string, string)"/> and optionally <see cref="SetBackgroundActivation"/>. For protocol activation, you should use <see cref="SetProtocolActivation(Uri)"/>. Add info that can be used by the application when the app was activated/launched by the toast.
/// </summary>
/// <param name="launchArgs">Custom app-defined launch arguments to be passed along on toast activation</param>
/// <param name="activationType">Set the activation type that will be used when the user click on this toast</param>
@ -67,6 +287,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
Content.Launch = launchArgs;
Content.ActivationType = activationType;
_customArgumentsUsedOnToastItself = true;
return this;
}
@ -93,6 +314,30 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return this;
}
#if WINRT
/// <summary>
/// Set custom audio to go along with the toast.
/// </summary>
/// <param name="src">Source to the media that will be played when the toast is pop</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
[Windows.Foundation.Metadata.DefaultOverload]
public ToastContentBuilder AddAudio(Uri src)
{
return AddAudio(src, default, default);
}
/// <summary>
/// Set custom audio to go along with the toast.
/// </summary>
/// <param name="src">Source to the media that will be played when the toast is pop</param>
/// <param name="loop">Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAudio(Uri src, bool? loop)
{
return AddAudio(src, loop, default);
}
#endif
/// <summary>
/// Set custom audio to go along with the toast.
/// </summary>
@ -100,7 +345,15 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <param name="loop">Indicating whether sound should repeat as long as the Toast is shown; false to play only once (default).</param>
/// <param name="silent">Indicating whether sound is muted; false to allow the Toast notification sound to play (default).</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAudio(Uri src, bool? loop = null, bool? silent = null)
public ToastContentBuilder AddAudio(
Uri src,
#if WINRT
bool? loop,
bool? silent)
#else
bool? loop = default,
bool? silent = default)
#endif
{
if (!src.IsFile)
{
@ -110,12 +363,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications
Content.Audio = new ToastAudio();
Content.Audio.Src = src;
if (loop != null)
if (loop != default)
{
Content.Audio.Loop = loop.Value;
}
if (silent != null)
if (silent != default)
{
Content.Audio.Silent = silent.Value;
}
@ -123,6 +376,17 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return this;
}
/// <summary>
/// Set custom audio to go along with the toast.
/// </summary>
/// <param name="audio">The <see cref="ToastAudio"/> to set.</param>
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAudio(ToastAudio audio)
{
Content.Audio = audio;
return this;
}
/// <summary>
/// Get the instance of <see cref="ToastContent"/> that has been built by the builder with specified configuration so far.
/// </summary>
@ -131,7 +395,111 @@ namespace Microsoft.Toolkit.Uwp.Notifications
{
return Content;
}
}
#if WINDOWS_UWP
/// <summary>
/// Retrieves the notification XML content as a WinRT XmlDocument, so that it can be used with a local Toast notification's constructor on either <see cref="Windows.UI.Notifications.ToastNotification"/> or <see cref="Windows.UI.Notifications.ScheduledToastNotification"/>.
/// </summary>
/// <returns>The notification XML content as a WinRT XmlDocument.</returns>
public Windows.Data.Xml.Dom.XmlDocument GetXml()
{
return GetToastContent().GetXml();
}
/// <summary>
/// Shows a new toast notification with the current content.
/// </summary>
public void Show()
{
CustomizeToast customize = null;
Show(customize);
}
/// <summary>
/// Shows a new toast notification with the current content.
/// </summary>
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ToastNotification"/> object.</param>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
#endif
public void Show(CustomizeToast customize)
{
var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
customize?.Invoke(notif);
ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
}
/// <summary>
/// Shows a new toast notification with the current content.
/// </summary>
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ToastNotification"/> object.</param>
/// <returns>An operation that completes after your async customizations have completed.</returns>
public Windows.Foundation.IAsyncAction Show(CustomizeToastAsync customize)
{
return ShowAsyncHelper(customize).AsAsyncAction();
}
private async System.Threading.Tasks.Task ShowAsyncHelper(CustomizeToastAsync customize)
{
var notif = new Windows.UI.Notifications.ToastNotification(GetToastContent().GetXml());
if (customize != null)
{
await customize.Invoke(notif);
}
ToastNotificationManagerCompat.CreateToastNotifier().Show(notif);
}
/// <summary>
/// Schedules the notification.
/// </summary>
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
public void Schedule(DateTimeOffset deliveryTime)
{
CustomizeScheduledToast customize = null;
Schedule(deliveryTime, customize);
}
/// <summary>
/// Schedules the notification.
/// </summary>
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ScheduledToastNotification"/> object.</param>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
#endif
public void Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToast customize)
{
var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
customize?.Invoke(notif);
ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
}
/// <summary>
/// Schedules the notification.
/// </summary>
/// <param name="deliveryTime">The date and time that Windows should display the toast notification. This time must be in the future.</param>
/// <param name="customize">Allows you to set additional properties on the <see cref="Windows.UI.Notifications.ScheduledToastNotification"/> object.</param>
/// <returns>An operation that completes after your async customizations have completed.</returns>
public Windows.Foundation.IAsyncAction Schedule(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize)
{
return ScheduleAsyncHelper(deliveryTime, customize).AsAsyncAction();
}
private async System.Threading.Tasks.Task ScheduleAsyncHelper(DateTimeOffset deliveryTime, CustomizeScheduledToastAsync customize = null)
{
var notif = new Windows.UI.Notifications.ScheduledToastNotification(GetToastContent().GetXml(), deliveryTime);
if (customize != null)
{
await customize.Invoke(notif);
}
ToastNotificationManagerCompat.CreateToastNotifier().AddToSchedule(notif);
}
#endif
}
}

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

@ -0,0 +1,102 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WIN32
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Windows.ApplicationModel;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
/// </summary>
internal class DesktopBridgeHelpers
{
private const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName);
private static bool? _hasIdentity;
public static bool HasIdentity()
{
if (_hasIdentity == null)
{
if (IsWindows7OrLower)
{
_hasIdentity = false;
}
else
{
int length = 0;
var sb = new StringBuilder(0);
GetCurrentPackageFullName(ref length, sb);
sb = new StringBuilder(length);
int error = GetCurrentPackageFullName(ref length, sb);
_hasIdentity = error != APPMODEL_ERROR_NO_PACKAGE;
}
}
return _hasIdentity.Value;
}
private static bool? _isContainerized;
/// <summary>
/// Returns true if the app is running in a container (MSIX) or false if not running in a container (sparse or plain Win32)
/// </summary>
/// <returns>Boolean</returns>
public static bool IsContainerized()
{
if (_isContainerized == null)
{
// If MSIX or sparse
if (HasIdentity())
{
// Sparse is identified if EXE is running outside of installed package location
var packageInstalledLocation = Package.Current.InstalledLocation.Path;
var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
// If inside package location
if (actualExeFullPath.StartsWith(packageInstalledLocation))
{
_isContainerized = true;
}
else
{
_isContainerized = false;
}
}
// Plain Win32
else
{
_isContainerized = false;
}
}
return _isContainerized.Value;
}
private static bool IsWindows7OrLower
{
get
{
int versionMajor = Environment.OSVersion.Version.Major;
int versionMinor = Environment.OSVersion.Version.Minor;
double version = versionMajor + ((double)versionMinor / 10);
return version <= 6.1;
}
}
}
}
#endif

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

@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WIN32
using System;
using System.Collections.Generic;
using Windows.UI.Notifications;
@ -10,6 +13,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <summary>
/// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts.
/// </summary>
[Obsolete("We recommend switching to the new ToastNotificationManagerCompat and ToastNotificationHistoryCompat.")]
public sealed class DesktopNotificationHistoryCompat
{
private string _aumid;
@ -99,4 +103,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
}
}
}
#endif

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