Merge branch 'jamesmcroft/3506-carousel-automation' of https://github.com/windows-toolkit/WindowsCommunityToolkit into jamesmcroft/3506-carousel-automation

This commit is contained in:
James Croft 2020-11-11 20:26:46 +00:00
Родитель a706ca6913 870b74398d
Коммит c9825f1c8b
224 изменённых файлов: 24172 добавлений и 1982 удалений

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

@ -7,10 +7,7 @@ assignees: ''
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
-->
<!-- 🚨 Please Do Not skip any instructions and information mentioned below as they are all required and essential to investigate the issue. Issues with missing information may be closed without investigation 🚨 -->
## Describe the bug
A clear and concise description of what the bug is.

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

@ -7,6 +7,8 @@ assignees: ''
---
<!-- 🚨 Please provide detailed information and Do Not skip any instructions as they are all required and essential to help us understand the feature 🚨 -->
## Describe the problem this feature would solve
<!-- Please describe or link to any existing issues or discussions.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->

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

@ -13,6 +13,7 @@ Hi!
We try and keep our GitHub issue list for bugs and features.
Ideally, it'd be great to post your question on Stack Overflow using the 'windows-community-toolkit' tag here: https://stackoverflow.com/questions/tagged/windows-community-toolkit
🚨 Please provide detailed information that includes examples, screenshots, and relevant issues if possible 🚨
If this is more about a scenario that you think is missing documentation, please file an issue instead at https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/issues/new

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

@ -1,3 +1,5 @@
<!-- 🚨 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 🚨 -->
## 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. -->
@ -38,7 +40,7 @@ Please check if your PR fulfills the following requirements:
- [ ] Contains **NO** breaking changes
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below.
Please note that breaking changes are likely to be rejected. -->
Please note that breaking changes are likely to be rejected within minor release cycles or held until major versions. -->
## Other information

6
.gitignore поставляемый
Просмотреть файл

@ -227,4 +227,8 @@ msbuild.binlog
!/build/tools/packages.config
# Generated file from .ttinclude
**/Generated/TypeInfo.g.cs
**/Generated/TypeInfo.g.cs
# TAEF Log output
WexLogFileOutput
*.wtl

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

@ -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/>

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

@ -24,7 +24,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>
{

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

@ -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

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

@ -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>
{

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

@ -16,7 +16,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>
{

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

@ -31,7 +31,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>
{

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

@ -422,11 +422,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 +443,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 +464,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 +488,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 +527,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 +536,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 +547,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
}
}
return ref Unsafe.AsRef<string>(null);
return ref Unsafe.NullRef<string>();
}
/// <summary>
@ -556,7 +556,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 +571,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 +588,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 +616,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 +628,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 +636,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 +662,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 +721,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 +733,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 +752,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 +764,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 +787,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;

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

@ -10,6 +10,7 @@ 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
{
@ -30,25 +31,14 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static ref T DangerousGetReference<T>(this T[] array)
{
#if NETCORE_RUNTIME
var arrayData = Unsafe.As<RawArrayData>(array);
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 +52,20 @@ 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);
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 +100,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 +178,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>
public 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>
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");
}
}
}

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

@ -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>
public static void ThrowArgumentOutOfRangeExceptionForDepth()
{
throw new ArgumentOutOfRangeException("depth");
}
}
}

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

@ -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 byte ToByte(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);
}
/// <summary>
/// Converts the given <see cref="bool"/> value into an <see cref="int"/>.
/// </summary>
@ -20,6 +34,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// <remarks>This method does not contain branching instructions.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use ToByte instead.")]
public static int ToInt(this bool flag)
{
return Unsafe.As<bool, byte>(ref flag);

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

@ -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);

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

@ -6,6 +6,9 @@ using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@ -15,6 +18,52 @@ 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>
/// 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);

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

@ -5,6 +5,10 @@
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory;
#endif
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;
namespace Microsoft.Toolkit.HighPerformance.Extensions
@ -14,6 +18,52 @@ 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>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="ReadOnlyMemory{T}"/> of <see cref="byte"/> instance.
/// </summary>
@ -27,6 +77,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;
}
@ -117,7 +138,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
public static ref readonly T DangerousGetLookupReferenceAt<T>(this ReadOnlySpan<T> span, int i)
{
// Check whether the input is in range by first casting both
// operands to uint and then comparing them, as this allows
@ -141,11 +162,57 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
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,13 +248,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 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>
@ -321,13 +374,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,14 +43,78 @@ 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.
@ -102,33 +169,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 +193,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 +264,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>

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

@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
#if NETCOREAPP3_1
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
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);
}

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

@ -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,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,275 @@
// 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 act as polyfill for .NET Standard 2.0 and below.
/// </summary>
internal static class RuntimeHelpers
{
/// <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]);
}
}
}
}

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

@ -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);
@ -82,44 +82,44 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#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;
@ -135,15 +135,15 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#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 +154,14 @@ 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)
while (chunkLength >= Vector<T>.Count)
{
ref T ri = ref Unsafe.Add(ref r0, offset);
@ -181,27 +182,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 +209,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 +233,88 @@ 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)
private static unsafe nint GetUpperBound<T>()
where T : unmanaged
{
if (sizeof(IntPtr) == 4)
if (typeof(T) == typeof(byte) ||
typeof(T) == typeof(sbyte) ||
typeof(T) == typeof(bool))
{
return (IntPtr)Math.Min((int)a, (int)b);
return sbyte.MaxValue;
}
return (IntPtr)Math.Min((long)a, (long)b);
if (typeof(T) == typeof(char) ||
typeof(T) == typeof(ushort) ||
typeof(T) == typeof(short))
{
return short.MaxValue;
}
if (typeof(T) == typeof(int) ||
typeof(T) == typeof(uint))
{
return int.MaxValue;
}
if (typeof(T) == typeof(long) ||
typeof(T) == typeof(ulong))
{
if (sizeof(nint) == sizeof(int))
{
return int.MaxValue;
}
// If we are on a 64 bit architecture and we are counting with a SIMD vector of 64
// bit values, we can use long.MaxValue as the upper bound, as a native integer will
// be able to contain such a value with no overflows. This will allow the count tight
// loop to process all the items in the target area in a single pass (except the mod).
// The (void*) cast is necessary to ensure the right constant is produced on runtimes
// before .NET 5 that don't natively support C# 9. For instance, removing that (void*)
// cast results in the value 0xFFFFFFFFFFFFFFFF (-1) instead of 0x7FFFFFFFFFFFFFFFF.
return (nint)(void*)long.MaxValue;
}
throw null!;
}
/// <summary>
/// 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"/>.
/// 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 <see cref="int"/>.</param>
/// <returns>The <see cref="int"/> cast of <paramref name="value"/>.</returns>
/// <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 int CastToInt<T>(T value)
private static nint CastToNativeInt<T>(T value)
where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
{
return Unsafe.As<T, sbyte>(ref value);
return (byte)(sbyte)(object)value;
}
if (typeof(T) == typeof(short))
{
return Unsafe.As<T, short>(ref value);
return (ushort)(short)(object)value;
}
if (typeof(T) == typeof(int))
{
return Unsafe.As<T, int>(ref value);
return (nint)(uint)(int)(object)value;
}
if (typeof(T) == typeof(long))
{
return (int)Unsafe.As<T, long>(ref value);
return (nint)(ulong)(long)(object)value;
}
throw new NotSupportedException($"Invalid input type {typeof(T)}");
throw null!;
}
}
}

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

@ -2,7 +2,6 @@
// 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;
@ -25,14 +24,13 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#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 +50,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 +61,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());
@ -92,11 +90,10 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers.Internals
#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 +104,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 +112,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 +160,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 +183,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 +225,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 +268,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 +307,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,903 @@
// 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;
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 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 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 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,921 @@
// 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;
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 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 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 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; }
}
}

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

@ -2,18 +2,19 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<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>
@ -75,6 +76,9 @@
</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
@ -88,7 +92,7 @@
</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>

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

@ -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>

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

@ -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();
}

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

@ -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();

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

@ -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
};
}
}
}

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

@ -25,23 +25,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

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

@ -2,18 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WIN32
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Windows.UI.Notifications;
using static Microsoft.Toolkit.Uwp.Notifications.NotificationActivator;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Helper for .NET Framework applications to display toast notifications and respond to toast events
/// </summary>
[Obsolete("We recommend switching to the new ToastNotificationManagerCompat. For Win32 apps, it no longer requires a Start menu shortcut, and activation now uses a straightforward event handler (no NotificationActivator class and COM GUIDs needed)!")]
public class DesktopNotificationManagerCompat
{
/// <summary>
@ -131,7 +133,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
// Create the instance of the .NET object
ppvObject = Marshal.GetComInterfaceForObject(
new T(),
typeof(INotificationActivationCallback));
typeof(NotificationActivator.INotificationActivationCallback));
}
else
{
@ -225,7 +227,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
/// <summary>
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs
/// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/master/DesktopBridge.Helpers/Helpers.cs
/// </summary>
private class DesktopBridgeHelpers
{
@ -273,3 +275,5 @@ namespace Microsoft.Toolkit.Uwp.Notifications
}
}
}
#endif

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

@ -0,0 +1,71 @@
// 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.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications.Internal
{
/// <summary>
/// Do not use this class. It must be public so that reflection can properly activate it, but consider it internal.
/// </summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public abstract class InternalNotificationActivator : InternalNotificationActivator.INotificationActivationCallback
{
/// <inheritdoc/>
public void Activate(string appUserModelId, string invokedArgs, InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data, uint dataCount)
{
ToastNotificationManagerCompat.OnActivatedInternal(invokedArgs, data, appUserModelId);
}
/// <summary>
/// A single user input key/value pair.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[Serializable]
public struct NOTIFICATION_USER_INPUT_DATA
{
/// <summary>
/// The key of the user input.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string Key;
/// <summary>
/// The value of the user input.
/// </summary>
[MarshalAs(UnmanagedType.LPWStr)]
public string Value;
}
/// <summary>
/// The COM callback that is triggered when your notification is clicked.
/// </summary>
[ComImport]
[Guid("53E31837-6600-4A81-9395-75CFFE746F94")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface INotificationActivationCallback
{
/// <summary>
/// The method called when your notification is clicked.
/// </summary>
/// <param name="appUserModelId">The app id of the app that sent the toast.</param>
/// <param name="invokedArgs">The activation arguments from the toast.</param>
/// <param name="data">The user input from the toast.</param>
/// <param name="dataCount">The number of user inputs.</param>
void Activate(
[In, MarshalAs(UnmanagedType.LPWStr)]
string appUserModelId,
[In, MarshalAs(UnmanagedType.LPWStr)]
string invokedArgs, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] NOTIFICATION_USER_INPUT_DATA[] data,
[In, MarshalAs(UnmanagedType.U4)]
uint dataCount);
}
}
}
#endif

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

@ -0,0 +1,91 @@
// 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.Xml;
using Windows.ApplicationModel;
namespace Microsoft.Toolkit.Uwp.Notifications
{
internal class ManifestHelper
{
private XmlDocument _doc;
private XmlNamespaceManager _namespaceManager;
public ManifestHelper()
{
var appxManifestPath = Path.Combine(Package.Current.InstalledLocation.Path, "AppxManifest.xml");
var doc = new XmlDocument();
doc.Load(appxManifestPath);
var namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("default", "http://schemas.microsoft.com/appx/manifest/foundation/windows10");
namespaceManager.AddNamespace("desktop", "http://schemas.microsoft.com/appx/manifest/desktop/windows10");
namespaceManager.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");
_doc = doc;
_namespaceManager = namespaceManager;
}
internal string GetAumidFromPackageManifest()
{
var appNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]", _namespaceManager);
if (appNode == null)
{
throw new InvalidOperationException("Your MSIX app manifest must have an <Application> entry.");
}
return Package.Current.Id.FamilyName + "!" + appNode.Attributes["Id"].Value;
}
internal string GetClsidFromPackageManifest()
{
var activatorClsidNode = _doc.SelectSingleNode("/default:Package/default:Applications/default:Application[1]/default:Extensions/desktop:Extension[@Category='windows.toastNotificationActivation']/desktop:ToastNotificationActivation/@ToastActivatorCLSID", _namespaceManager);
if (activatorClsidNode == null)
{
throw new InvalidOperationException("Your app manifest must have a toastNotificationActivation extension with a valid ToastActivatorCLSID specified.");
}
var clsid = activatorClsidNode.Value;
// Make sure they have a COM class registration matching the CLSID
var comClassNode = _doc.SelectSingleNode($"/default:Package/default:Applications/default:Application[1]/default:Extensions/com:Extension[@Category='windows.comServer']/com:ComServer/com:ExeServer/com:Class[@Id='{clsid}']", _namespaceManager);
if (comClassNode == null)
{
throw new InvalidOperationException("Your app manifest must have a comServer extension with a class ID matching your toastNotificationActivator's CLSID.");
}
// Make sure they have a COM class registration matching their current process executable
var comExeServerNode = comClassNode.ParentNode;
var specifiedExeRelativePath = comExeServerNode.Attributes["Executable"].Value;
var specifiedExeFullPath = Path.Combine(Package.Current.InstalledLocation.Path, specifiedExeRelativePath);
var actualExeFullPath = Process.GetCurrentProcess().MainModule.FileName;
if (specifiedExeFullPath != actualExeFullPath)
{
var correctExeRelativePath = actualExeFullPath.Substring(Package.Current.InstalledLocation.Path.Length + 1);
throw new InvalidOperationException($"Your app manifest's comServer extension's Executable value is incorrect. It should be \"{correctExeRelativePath}\".");
}
// Make sure their arguments are set correctly
var argumentsNode = comExeServerNode.Attributes.GetNamedItem("Arguments");
if (argumentsNode == null || argumentsNode.Value != "-ToastActivated")
{
throw new InvalidOperationException("Your app manifest's comServer extension for toast activation must have its Arguments set exactly to \"-ToastActivated\"");
}
return clsid;
}
}
}
#endif

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

@ -0,0 +1,20 @@
// 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.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[ComImport]
[Guid("660b90c8-73a9-4b58-8cae-355b7f55341b")]
[ClassInterface(ClassInterfaceType.None)]
internal class CAppResolver
{
}
}
#endif

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

@ -0,0 +1,80 @@
// 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.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[ComImport]
[Guid("DE25675A-72DE-44b4-9373-05170450C140")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IApplicationResolver
{
void GetAppIDForShortcut(
IntPtr psi,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
void GetAppIDForShortcutObject(
IntPtr psl,
IntPtr psi,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
void GetAppIDForWindow(
int hwnd,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
[Out] out bool pfPinningPrevented,
[Out] out bool pfExplicitAppID,
[Out] out bool pfEmbeddedShortcutValid);
/// <summary>
/// Main way to obtain app ID for any process. Calls GetShortcutPathOrAppIdFromPid
/// </summary>
void GetAppIDForProcess(
uint dwProcessID,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
[Out] out bool pfPinningPrevented,
[Out] out bool pfExplicitAppID,
[Out] out bool pfEmbeddedShortcutValid);
void GetShortcutForProcess(
uint dwProcessID,
[Out] out IntPtr ppsi);
void GetBestShortcutForAppID(
string pszAppID,
[Out] out IShellItem ppsi);
void GetBestShortcutAndAppIDForAppPath(
string pszAppPath,
[Out] out IntPtr ppsi,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID);
void CanPinApp(IntPtr psi);
void CanPinAppShortcut(
IntPtr psl,
IntPtr psi);
void GetRelaunchProperties(
int hwnd,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszAppID,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszCmdLine,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszIconResource,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszDisplayNameResource,
[Out] bool pfPinnable);
void GenerateShortcutFromWindowProperties(
int hwnd,
[Out] out IntPtr ppsi);
void GenerateShortcutFromItemProperties(
IntPtr psi2,
[Out] out IntPtr ppsi);
}
}
#endif

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

@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#if WIN32
using System;
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[ComImport]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItem
{
void BindToHandler(
IntPtr pbc,
IntPtr bhid,
IntPtr riid,
[Out] out IntPtr ppv);
void GetParent(
[Out] out IShellItem ppsi);
void GetDisplayName(
int sigdnName,
[Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
void GetAttributes(
int sfgaoMask,
[Out] out int psfgaoAttribs);
void Compare(
IShellItem psi,
int hint,
[Out] out int piOrder);
}
}
#endif

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

@ -0,0 +1,24 @@
// 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.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[ComImport]
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IShellItemImageFactory
{
void GetImage(
[In, MarshalAs(UnmanagedType.Struct)] SIZE size,
[In] SIIGBF flags,
[Out] out IntPtr phbm);
}
}
#endif

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

@ -0,0 +1,24 @@
// 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;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[Flags]
internal enum SIIGBF
{
ResizeToFit = 0x00,
BiggerSizeOk = 0x01,
MemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
ScaleUp = 0x00000100
}
}
#endif

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

@ -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.
#if WIN32
using System.Runtime.InteropServices;
namespace Microsoft.Toolkit.Uwp.Notifications
{
[StructLayout(LayoutKind.Sequential)]
internal struct SIZE
{
internal int X;
internal int Y;
internal SIZE(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
#endif

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

@ -2,6 +2,8 @@
// 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;
using System.Collections.Generic;
@ -16,6 +18,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <summary>
/// Apps must implement this activator to handle notification activation.
/// </summary>
[Obsolete("You can now subscribe to activation by simpy using the ToastNotificationManagerCompat.OnActivated event. We recommend deleting your NotificationActivator and switching to using the event.")]
public abstract class NotificationActivator : NotificationActivator.INotificationActivationCallback
{
/// <inheritdoc/>
@ -77,4 +80,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
uint dataCount);
}
}
}
}
#endif

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

@ -2,6 +2,8 @@
// 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;
using System.Collections.Generic;
@ -18,9 +20,13 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// </summary>
public class NotificationUserInput : IReadOnlyDictionary<string, string>
{
#pragma warning disable CS0618 // Type or member is obsolete
private NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] _data;
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
internal NotificationUserInput(NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data)
#pragma warning restore CS0618 // Type or member is obsolete
{
_data = data;
}
@ -92,4 +98,6 @@ namespace Microsoft.Toolkit.Uwp.Notifications
return GetEnumerator();
}
}
}
}
#endif

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

@ -0,0 +1,16 @@
// 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
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Event triggered when a notification is clicked.
/// </summary>
/// <param name="e">Arguments that specify what action was taken and the user inputs.</param>
public delegate void OnActivated(ToastNotificationActivatedEventArgsCompat e);
}
#endif

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

@ -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.
#if WIN32
using Windows.Foundation.Collections;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Provides information about an event that occurs when the app is activated because a user tapped on the body of a toast notification or performed an action inside a toast notification.
/// </summary>
public class ToastNotificationActivatedEventArgsCompat
{
internal ToastNotificationActivatedEventArgsCompat()
{
}
/// <summary>
/// Gets the arguments from the toast XML payload related to the action that was invoked on the toast.
/// </summary>
public string Argument { get; internal set; }
/// <summary>
/// Gets a set of values that you can use to obtain the user input from an interactive toast notification.
/// </summary>
public ValueSet UserInput { get; internal set; }
}
}
#endif

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

@ -0,0 +1,254 @@
// 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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.Toolkit.Uwp.Notifications
{
internal class Win32AppInfo
{
public string Aumid { get; set; }
public string DisplayName { get; set; }
public string IconPath { get; set; }
public static Win32AppInfo Get()
{
var process = Process.GetCurrentProcess();
// First get the app ID
IApplicationResolver appResolver = (IApplicationResolver)new CAppResolver();
appResolver.GetAppIDForProcess(Convert.ToUInt32(process.Id), out string appId, out _, out _, out _);
// Then try to get the shortcut (for display name and icon)
IShellItem shortcutItem = null;
try
{
appResolver.GetBestShortcutForAppID(appId, out shortcutItem);
}
catch
{
}
string displayName = null;
string iconPath = null;
// First we attempt to use display assets from the shortcut itself
if (shortcutItem != null)
{
try
{
shortcutItem.GetDisplayName(0, out displayName);
((IShellItemImageFactory)shortcutItem).GetImage(new SIZE(48, 48), SIIGBF.IconOnly | SIIGBF.BiggerSizeOk, out IntPtr nativeHBitmap);
if (nativeHBitmap != IntPtr.Zero)
{
try
{
Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);
if (IsAlphaBitmap(bmp, out var bmpData))
{
var alphaBitmap = GetAlphaBitmapFromBitmapData(bmpData);
iconPath = SaveIconToAppPath(alphaBitmap, appId);
}
else
{
iconPath = SaveIconToAppPath(bmp, appId);
}
}
catch
{
}
DeleteObject(nativeHBitmap);
}
}
catch
{
}
}
// If we didn't get a display name from shortcut
if (string.IsNullOrWhiteSpace(displayName))
{
// We use the one from the process
displayName = GetDisplayNameFromCurrentProcess(process);
}
// If we didn't get an icon from shortcut
if (string.IsNullOrWhiteSpace(iconPath))
{
// We use the one from the process
iconPath = ExtractAndObtainIconFromCurrentProcess(process, appId);
}
return new Win32AppInfo()
{
Aumid = appId,
DisplayName = displayName,
IconPath = iconPath
};
}
private static string GetDisplayNameFromCurrentProcess(Process process)
{
// If AssemblyTitle is set, use that
var assemblyTitleAttr = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyTitleAttribute>();
if (assemblyTitleAttr != null)
{
return assemblyTitleAttr.Title;
}
// Otherwise, fall back to process name
return process.ProcessName;
}
private static string ExtractAndObtainIconFromCurrentProcess(Process process, string appId)
{
return ExtractAndObtainIconFromPath(process.MainModule.FileName, appId);
}
private static string ExtractAndObtainIconFromPath(string pathToExtract, string appId)
{
try
{
// Extract the icon
var icon = Icon.ExtractAssociatedIcon(pathToExtract);
using (var bmp = icon.ToBitmap())
{
return SaveIconToAppPath(bmp, appId);
}
}
catch
{
return null;
}
}
private static string SaveIconToAppPath(Bitmap bitmap, string appId)
{
try
{
var path = Path.Combine(GetAppDataFolderPath(appId), "Icon.png");
// Ensure the directories exist
Directory.CreateDirectory(Path.GetDirectoryName(path));
bitmap.Save(path, ImageFormat.Png);
return path;
}
catch
{
return null;
}
}
/// <summary>
/// Gets the app data folder path within the ToastNotificationManagerCompat folder, used for storing icon assets and any additional data.
/// </summary>
/// <returns>Returns a string of the absolute folder path.</returns>
public static string GetAppDataFolderPath(string appId)
{
string conciseAumid = appId.Contains("\\") ? GenerateGuid(appId) : appId;
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ToastNotificationManagerCompat", "Apps", conciseAumid);
}
// From https://stackoverflow.com/a/9291151
private static Bitmap GetAlphaBitmapFromBitmapData(BitmapData bmpData)
{
return new Bitmap(
bmpData.Width,
bmpData.Height,
bmpData.Stride,
PixelFormat.Format32bppArgb,
bmpData.Scan0);
}
// From https://stackoverflow.com/a/9291151
private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
{
var bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);
bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
for (int y = 0; y <= bmpData.Height - 1; y++)
{
for (int x = 0; x <= bmpData.Width - 1; x++)
{
var pixelColor = Color.FromArgb(
Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));
if (pixelColor.A > 0 & pixelColor.A < 255)
{
return true;
}
}
}
}
finally
{
bmp.UnlockBits(bmpData);
}
return false;
}
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject([In] IntPtr hObject);
/// <summary>
/// From https://stackoverflow.com/a/41622689/1454643
/// Generates Guid based on String. Key assumption for this algorithm is that name is unique (across where it it's being used)
/// and if name byte length is less than 16 - it will be fetched directly into guid, if over 16 bytes - then we compute sha-1
/// hash from string and then pass it to guid.
/// </summary>
/// <param name="name">Unique name which is unique across where this guid will be used.</param>
/// <returns>For example "706C7567-696E-7300-0000-000000000000" for "plugins"</returns>
public static string GenerateGuid(string name)
{
byte[] buf = Encoding.UTF8.GetBytes(name);
byte[] guid = new byte[16];
if (buf.Length < 16)
{
Array.Copy(buf, guid, buf.Length);
}
else
{
using (SHA1 sha1 = SHA1.Create())
{
byte[] hash = sha1.ComputeHash(buf);
// Hash is 20 bytes, but we need 16. We loose some of "uniqueness", but I doubt it will be fatal
Array.Copy(hash, guid, 16);
}
}
// Don't use Guid constructor, it tends to swap bytes. We want to preserve original string as hex dump.
string guidS = $"{guid[0]:X2}{guid[1]:X2}{guid[2]:X2}{guid[3]:X2}-{guid[4]:X2}{guid[5]:X2}-{guid[6]:X2}{guid[7]:X2}-{guid[8]:X2}{guid[9]:X2}-{guid[10]:X2}{guid[11]:X2}{guid[12]:X2}{guid[13]:X2}{guid[14]:X2}{guid[15]:X2}";
return guidS;
}
}
}
#endif

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

@ -0,0 +1,101 @@
// 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 System.Collections.Generic;
using Windows.UI.Notifications;
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>
public class ToastNotificationHistoryCompat
{
private string _aumid;
private ToastNotificationHistory _history;
internal ToastNotificationHistoryCompat(string aumid)
{
_aumid = aumid;
_history = ToastNotificationManager.History;
}
/// <summary>
/// Removes all notifications sent by this app from action center.
/// </summary>
public void Clear()
{
if (_aumid != null)
{
_history.Clear(_aumid);
}
else
{
_history.Clear();
}
}
/// <summary>
/// Gets all notifications sent by this app that are currently still in Action Center.
/// </summary>
/// <returns>A collection of toasts.</returns>
public IReadOnlyList<ToastNotification> GetHistory()
{
return _aumid != null ? _history.GetHistory(_aumid) : _history.GetHistory();
}
/// <summary>
/// Removes an individual toast, with the specified tag label, from action center.
/// </summary>
/// <param name="tag">The tag label of the toast notification to be removed.</param>
public void Remove(string tag)
{
if (_aumid != null)
{
_history.Remove(tag, string.Empty, _aumid);
}
else
{
_history.Remove(tag);
}
}
/// <summary>
/// Removes a toast notification from the action using the notification's tag and group labels.
/// </summary>
/// <param name="tag">The tag label of the toast notification to be removed.</param>
/// <param name="group">The group label of the toast notification to be removed.</param>
public void Remove(string tag, string group)
{
if (_aumid != null)
{
_history.Remove(tag, group, _aumid);
}
else
{
_history.Remove(tag, group);
}
}
/// <summary>
/// Removes a group of toast notifications, identified by the specified group label, from action center.
/// </summary>
/// <param name="group">The group label of the toast notifications to be removed.</param>
public void RemoveGroup(string group)
{
if (_aumid != null)
{
_history.RemoveGroup(group, _aumid);
}
else
{
_history.RemoveGroup(group);
}
}
}
}
#endif

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

@ -0,0 +1,478 @@
// 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 System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Windows.ApplicationModel;
using Windows.Foundation.Collections;
using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// Provides access to sending and managing toast notifications. Works for all types of apps, even Win32 non-MSIX/sparse apps.
/// </summary>
public static class ToastNotificationManagerCompat
{
#if WIN32
private const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated";
private const int CLASS_E_NOAGGREGATION = -2147221232;
private const int E_NOINTERFACE = -2147467262;
private const int CLSCTX_LOCAL_SERVER = 4;
private const int REGCLS_MULTIPLEUSE = 1;
private const int S_OK = 0;
private static readonly Guid IUnknownGuid = new Guid("00000000-0000-0000-C000-000000000046");
private static bool _registeredOnActivated;
private static List<OnActivated> _onActivated = new List<OnActivated>();
/// <summary>
/// Event that is triggered when a notification or notification button is clicked. Subscribe to this event in your app's initial startup code.
/// </summary>
public static event OnActivated OnActivated
{
add
{
lock (_onActivated)
{
if (!_registeredOnActivated)
{
// Desktop Bridge apps will dynamically register upon first subscription to event
try
{
CreateAndRegisterActivator();
}
catch (Exception ex)
{
_initializeEx = ex;
}
}
_onActivated.Add(value);
}
}
remove
{
lock (_onActivated)
{
_onActivated.Remove(value);
}
}
}
internal static void OnActivatedInternal(string args, Internal.InternalNotificationActivator.NOTIFICATION_USER_INPUT_DATA[] input, string aumid)
{
ValueSet userInput = new ValueSet();
if (input != null)
{
foreach (var val in input)
{
userInput.Add(val.Key, val.Value);
}
}
var e = new ToastNotificationActivatedEventArgsCompat()
{
Argument = args,
UserInput = userInput
};
OnActivated[] listeners;
lock (_onActivated)
{
listeners = _onActivated.ToArray();
}
foreach (var listener in listeners)
{
listener(e);
}
}
private static string _win32Aumid;
private static string _clsid;
private static Exception _initializeEx;
static ToastNotificationManagerCompat()
{
try
{
Initialize();
}
catch (Exception ex)
{
// We catch the exception so that things like subscribing to the event handler doesn't crash app
_initializeEx = ex;
}
}
private static void Initialize()
{
// If containerized
if (DesktopBridgeHelpers.IsContainerized())
{
// No need to do anything additional, already registered through manifest
return;
}
Win32AppInfo win32AppInfo = null;
// If sparse
if (DesktopBridgeHelpers.HasIdentity())
{
_win32Aumid = new ManifestHelper().GetAumidFromPackageManifest();
}
else
{
win32AppInfo = Win32AppInfo.Get();
_win32Aumid = win32AppInfo.Aumid;
}
// Create and register activator
var activatorType = CreateAndRegisterActivator();
// Register via registry
using (var rootKey = Registry.CurrentUser.CreateSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid))
{
// If they don't have identity, we need to specify the display assets
if (!DesktopBridgeHelpers.HasIdentity())
{
// Set the display name and icon uri
rootKey.SetValue("DisplayName", win32AppInfo.DisplayName);
if (win32AppInfo.IconPath != null)
{
rootKey.SetValue("IconUri", win32AppInfo.IconPath);
}
else
{
if (rootKey.GetValue("IconUri") != null)
{
rootKey.DeleteValue("IconUri");
}
}
// Background color only appears in the settings page, format is
// hex without leading #, like "FFDDDDDD"
rootKey.SetValue("IconBackgroundColor", "FFDDDDDD");
}
rootKey.SetValue("CustomActivator", string.Format("{{{0}}}", activatorType.GUID));
}
}
private static Type CreateActivatorType()
{
// https://stackoverflow.com/questions/24069352/c-sharp-typebuilder-generate-class-with-function-dynamically
// For .NET Core we use https://stackoverflow.com/questions/36937276/is-there-any-replace-of-assemblybuilder-definedynamicassembly-in-net-core
AssemblyName aName = new AssemblyName("DynamicComActivator");
AssemblyBuilder aBuilder = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
// For a single-module assembly, the module name is usually the assembly name plus an extension.
ModuleBuilder mb = aBuilder.DefineDynamicModule(aName.Name);
// Create class which extends NotificationActivator
TypeBuilder tb = mb.DefineType(
name: "MyNotificationActivator",
attr: TypeAttributes.Public,
parent: typeof(Internal.InternalNotificationActivator),
interfaces: new Type[0]);
if (DesktopBridgeHelpers.IsContainerized())
{
_clsid = new ManifestHelper().GetClsidFromPackageManifest();
}
else
{
_clsid = Win32AppInfo.GenerateGuid(_win32Aumid);
}
tb.SetCustomAttribute(new CustomAttributeBuilder(
con: typeof(GuidAttribute).GetConstructor(new Type[] { typeof(string) }),
constructorArgs: new object[] { _clsid }));
tb.SetCustomAttribute(new CustomAttributeBuilder(
con: typeof(ComVisibleAttribute).GetConstructor(new Type[] { typeof(bool) }),
constructorArgs: new object[] { true }));
tb.SetCustomAttribute(new CustomAttributeBuilder(
#pragma warning disable CS0618 // Type or member is obsolete
con: typeof(ComSourceInterfacesAttribute).GetConstructor(new Type[] { typeof(Type) }),
#pragma warning restore CS0618 // Type or member is obsolete
constructorArgs: new object[] { typeof(Internal.InternalNotificationActivator.INotificationActivationCallback) }));
tb.SetCustomAttribute(new CustomAttributeBuilder(
con: typeof(ClassInterfaceAttribute).GetConstructor(new Type[] { typeof(ClassInterfaceType) }),
constructorArgs: new object[] { ClassInterfaceType.None }));
return tb.CreateType();
}
private static Type CreateAndRegisterActivator()
{
var activatorType = CreateActivatorType();
RegisterActivator(activatorType);
_registeredOnActivated = true;
return activatorType;
}
private static void RegisterActivator(Type activatorType)
{
if (!DesktopBridgeHelpers.IsContainerized())
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
RegisterComServer(activatorType, exePath);
}
// Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest
var uuid = activatorType.GUID;
CoRegisterClassObject(
uuid,
new NotificationActivatorClassFactory(activatorType),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
out _);
}
private static void RegisterComServer(Type activatorType, string exePath)
{
// We register the EXE to start up when the notification is activated
string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", activatorType.GUID);
var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString);
// Include a flag so we know this was a toast activation and should wait for COM to process
// We also wrap EXE path in quotes for extra security
key.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG);
}
/// <summary>
/// Gets whether the current process was activated due to a toast activation. If so, the OnActivated event will be triggered soon after process launch.
/// </summary>
/// <returns>True if the current process was activated due to a toast activation, otherwise false.</returns>
public static bool WasCurrentProcessToastActivated()
{
return Environment.GetCommandLineArgs().Contains(TOAST_ACTIVATED_LAUNCH_ARG);
}
[ComImport]
[Guid("00000001-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
private class NotificationActivatorClassFactory : IClassFactory
{
private Type _activatorType;
public NotificationActivatorClassFactory(Type activatorType)
{
_activatorType = activatorType;
}
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == _activatorType.GUID || riid == IUnknownGuid)
{
// Create the instance of the .NET object
ppvObject = Marshal.GetComInterfaceForObject(
Activator.CreateInstance(_activatorType),
typeof(Internal.InternalNotificationActivator.INotificationActivationCallback));
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
return S_OK;
}
public int LockServer(bool fLock)
{
return S_OK;
}
}
[DllImport("ole32.dll")]
private static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
#endif
/// <summary>
/// Creates a toast notifier.
/// </summary>
/// <returns><see cref="ToastNotifier"/></returns>
public static ToastNotifier CreateToastNotifier()
{
#if WIN32
if (_initializeEx != null)
{
throw _initializeEx;
}
if (DesktopBridgeHelpers.HasIdentity())
{
return ToastNotificationManager.CreateToastNotifier();
}
else
{
return ToastNotificationManager.CreateToastNotifier(_win32Aumid);
}
#else
return ToastNotificationManager.CreateToastNotifier();
#endif
}
/// <summary>
/// Gets the <see cref="ToastNotificationHistoryCompat"/> object.
/// </summary>
public static ToastNotificationHistoryCompat History
{
get
{
#if WIN32
if (_initializeEx != null)
{
throw _initializeEx;
}
return new ToastNotificationHistoryCompat(DesktopBridgeHelpers.HasIdentity() ? null : _win32Aumid);
#else
return new ToastNotificationHistoryCompat(null);
#endif
}
}
/// <summary>
/// Gets a value indicating whether http images can be used within toasts. This is true if running with package identity (UWP, MSIX, or sparse package).
/// </summary>
public static bool CanUseHttpImages
{
get
{
#if WIN32
return DesktopBridgeHelpers.HasIdentity();
#else
return true;
#endif
}
}
#if WIN32
/// <summary>
/// If you're not using MSIX, call this when your app is being uninstalled to properly clean up all notifications and notification-related resources. Note that this must be called from your app's main EXE (the one that you used notifications for) and not a separate uninstall EXE. If called from a MSIX app, this method no-ops.
/// </summary>
public static void Uninstall()
{
if (DesktopBridgeHelpers.IsContainerized())
{
// Packaged containerized apps automatically clean everything up already
return;
}
if (!DesktopBridgeHelpers.HasIdentity())
{
try
{
// Remove all scheduled notifications (do this first before clearing current notifications)
var notifier = CreateToastNotifier();
foreach (var scheduled in CreateToastNotifier().GetScheduledToastNotifications())
{
try
{
notifier.RemoveFromSchedule(scheduled);
}
catch
{
}
}
}
catch
{
}
try
{
// Clear all current notifications
History.Clear();
}
catch
{
}
}
try
{
// Remove registry key
if (_win32Aumid != null)
{
Registry.CurrentUser.DeleteSubKey(@"Software\Classes\AppUserModelId\" + _win32Aumid);
}
}
catch
{
}
try
{
if (_clsid != null)
{
Registry.CurrentUser.DeleteSubKey(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", _clsid));
}
}
catch
{
}
if (!DesktopBridgeHelpers.HasIdentity() && _win32Aumid != null)
{
try
{
// Delete any of the app files
var appDataFolderPath = Win32AppInfo.GetAppDataFolderPath(_win32Aumid);
if (Directory.Exists(appDataFolderPath))
{
Directory.Delete(appDataFolderPath, recursive: true);
}
}
catch
{
}
}
}
#endif
}
}
#endif

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

@ -0,0 +1,96 @@
// 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.Uwp.Notifications
{
/// <summary>
/// Interfaces for classes that can have activation info added to them.
/// </summary>
/// <typeparam name="T">The type of the host object.</typeparam>
internal interface IToastActivateableBuilder<T>
{
/// <summary>
/// Adds a key (without value) to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The current instance of the object.</returns>
T AddArgument(string key);
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of the object.</returns>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
#endif
T AddArgument(string key, string value);
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of the object.</returns>
T AddArgument(string key, int value);
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of the object.</returns>
T AddArgument(string key, double value);
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of the object.</returns>
T AddArgument(string key, float value);
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is clicked.
/// </summary>
/// <param name="key">The key for this value.</param>
/// <param name="value">The value itself.</param>
/// <returns>The current instance of the object.</returns>
T AddArgument(string key, bool value);
#if !WINRT
/// <summary>
/// Adds a key/value to the activation arguments that will be returned when the content is 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 the object.</returns>
T AddArgument(string key, Enum value);
#endif
/// <summary>
/// Configures the content to use background activation when it is clicked.
/// </summary>
/// <returns>The current instance of the object.</returns>
T SetBackgroundActivation();
/// <summary>
/// Configures the content to use protocol activation when it is clicked.
/// </summary>
/// <param name="protocol">The protocol to launch.</param>
/// <returns>The current instance of the object.</returns>
T SetProtocolActivation(Uri protocol);
/// <summary>
/// Configures the content to use protocol activation when it 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 the object.</returns>
T SetProtocolActivation(Uri protocol, string targetApplicationPfn);
}
}

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

@ -0,0 +1,462 @@
// 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;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// A class that supports serializing simple key/value pairs into a format that's friendly for being used within toast notifications. The serialized format is similar to a query string, however optimized for being placed within an XML property (uses semicolons instead of ampersands since those don't need to be XML-escaped, doesn't url-encode all special characters since not being used within a URL, etc).
/// </summary>
public sealed class ToastArguments : IEnumerable<KeyValuePair<string, string>>
{
private Dictionary<string, string> _dictionary = new Dictionary<string, string>();
internal ToastArguments Clone()
{
return new ToastArguments()
{
_dictionary = new Dictionary<string, string>(_dictionary)
};
}
#if !WINRT
/// <summary>
/// Gets the value of the specified key. Throws <see cref="KeyNotFoundException"/> if the key could not be found.
/// </summary>
/// <param name="key">The key to find.</param>
/// <returns>The value of the specified key.</returns>
public string this[string key]
{
get
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (TryGetValue(key, out string value))
{
return value;
}
throw new KeyNotFoundException($"A key with name '{key}' could not be found.");
}
set
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
_dictionary[key] = value;
}
}
#endif
/// <summary>
/// Attempts to get the value of the specified key. If no key exists, returns false.
/// </summary>
/// <param name="key">The key to find.</param>
/// <param name="value">The key's value will be written here if found.</param>
/// <returns>True if found the key and set the value, otherwise false.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("found")]
#endif
public bool TryGetValue(string key, out string value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _dictionary.TryGetValue(key, out value);
}
#if !WINRT
/// <summary>
/// Attempts to get the value of the specified key. If no key exists, returns false.
/// </summary>
/// <typeparam name="T">The enum to parse.</typeparam>
/// <param name="key">The key to find.</param>
/// <param name="value">The key's value will be written here if found.</param>
/// <returns>True if found the key and set the value, otherwise false.</returns>
public bool TryGetValue<T>(string key, out T value)
where T : struct, Enum
{
if (TryGetValue(key, out string strValue))
{
return Enum.TryParse(strValue, out value);
}
value = default(T);
return false;
}
#endif
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public string Get(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (_dictionary.TryGetValue(key, out string value))
{
return value;
}
throw new KeyNotFoundException();
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public int GetInt(string key)
{
return int.Parse(Get(key));
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public double GetDouble(string key)
{
return double.Parse(Get(key));
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public float GetFloat(string key)
{
return float.Parse(Get(key));
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public byte GetByte(string key)
{
return byte.Parse(Get(key));
}
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public bool GetBool(string key)
{
return Get(key) == "1" ? true : false;
}
#if !WINRT
/// <summary>
/// Gets the value of the specified key, or throws <see cref="KeyNotFoundException"/> if key didn't exist.
/// </summary>
/// <typeparam name="T">The enum to parse.</typeparam>
/// <param name="key">The key to get.</param>
/// <returns>The value of the key.</returns>
public T GetEnum<T>(string key)
where T : struct, Enum
{
if (TryGetValue(key, out T value))
{
return value;
}
throw new KeyNotFoundException();
}
#endif
/// <summary>
/// Gets the number of key/value pairs contained in the toast arguments.
/// </summary>
public int Count => _dictionary.Count;
/// <summary>
/// Adds a key. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
_dictionary[key] = null;
return this;
}
/// <summary>
/// Adds a key and optional value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The optional value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
_dictionary[key] = value;
return this;
}
/// <summary>
/// Adds a key and value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key, int value)
{
return AddHelper(key, value);
}
/// <summary>
/// Adds a key and value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key, double value)
{
return AddHelper(key, value);
}
/// <summary>
/// Adds a key and value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key, float value)
{
return AddHelper(key, value);
}
/// <summary>
/// Adds a key and value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key.</param>
/// <returns>The current <see cref="ToastArguments"/> object.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("toastArguments")]
#endif
public ToastArguments Add(string key, bool value)
{
return Add(key, value ? "1" : "0"); // Encode as 1 or 0 to save string space
}
#if !WINRT
/// <summary>
/// Adds a key and value. If there is an existing key, it is replaced.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value of the key. 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 <see cref="ToastArguments"/> object.</returns>
public ToastArguments Add(string key, Enum value)
{
return Add(key, (int)(object)value);
}
#endif
private ToastArguments AddHelper(string key, object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
_dictionary[key] = value.ToString();
return this;
}
/// <summary>
/// Determines if the specified key is present.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>True if the key is present, otherwise false.</returns>
public bool Contains(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _dictionary.ContainsKey(key);
}
/// <summary>
/// Determines if specified key and value are present.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <param name="value">The value to look for when the key has been matched.</param>
/// <returns>True if the key and value were found, else false.</returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("doesContain")]
#endif
public bool Contains(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _dictionary.TryGetValue(key, out string actualValue) && actualValue == value;
}
/// <summary>
/// Removes the specified key and its associated value.
/// </summary>
/// <param name="key">The key to remove.</param>
/// <returns>True if the key was removed, else false.</returns>
public bool Remove(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
return _dictionary.Remove(key);
}
private static string Encode(string str)
{
return str
.Replace("%", "%25")
.Replace(";", "%3B")
.Replace("=", "%3D");
}
private static string Decode(string str)
{
return str
.Replace("%25", "%")
.Replace("%3B", ";")
.Replace("%3D", "=");
}
/// <summary>
/// Parses a string that was generated using ToastArguments into a <see cref="ToastArguments"/> object.
/// </summary>
/// <param name="toastArgumentsStr">The toast arguments string to deserialize.</param>
/// <returns>The parsed toast arguments.</returns>
public static ToastArguments Parse(string toastArgumentsStr)
{
if (string.IsNullOrWhiteSpace(toastArgumentsStr))
{
return new ToastArguments();
}
string[] pairs = toastArgumentsStr.Split(';');
ToastArguments answer = new ToastArguments();
foreach (string pair in pairs)
{
string name;
string value;
int indexOfEquals = pair.IndexOf('=');
if (indexOfEquals == -1)
{
name = Decode(pair);
value = null;
}
else
{
name = Decode(pair.Substring(0, indexOfEquals));
value = Decode(pair.Substring(indexOfEquals + 1));
}
answer.Add(name, value);
}
return answer;
}
/// <summary>
/// Serializes the key-value pairs into a string that can be used within a toast notification.
/// </summary>
/// <returns>A string that can be used within a toast notification.</returns>
public sealed override string ToString()
{
return string.Join(Separator, this.Select(pair => EncodePair(pair.Key, pair.Value)));
}
internal static string EncodePair(string key, string value)
{
// Key
return Encode(key) +
// Write value if not null
((value == null) ? string.Empty : ("=" + Encode(value)));
}
internal const string Separator = ";";
/// <summary>
/// Gets an enumerator to enumerate the arguments. Note that order of the arguments is NOT preserved.
/// </summary>
/// <returns>An enumeartor of the key/value pairs.</returns>
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
/// <summary>
/// Gets an enumerator to enumerate the query string parameters.
/// </summary>
/// <returns>An enumeartor of the key/value pairs.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

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

@ -3,14 +3,23 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
namespace Microsoft.Toolkit.Uwp.Notifications
{
/// <summary>
/// A button that the user can click on a Toast notification.
/// </summary>
public sealed class ToastButton : IToastButton
public sealed class ToastButton :
#if !WINRT
IToastActivateableBuilder<ToastButton>,
#endif
IToastButton
{
private Dictionary<string, string> _arguments = new Dictionary<string, string>();
private bool _usingCustomArguments;
/// <summary>
/// Initializes a new instance of the <see cref="ToastButton"/> class.
/// </summary>
@ -30,6 +39,17 @@ namespace Microsoft.Toolkit.Uwp.Notifications
Content = content;
Arguments = arguments;
_usingCustomArguments = arguments.Length > 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ToastButton"/> class.
/// </summary>
public ToastButton()
{
// Arguments are required (we'll initialize to empty string which is fine).
Arguments = string.Empty;
}
/// <summary>
@ -41,7 +61,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// Gets app-defined string of arguments that the app can later retrieve once it is
/// activated when the user clicks the button. Required
/// </summary>
public string Arguments { get; private set; }
public string Arguments { get; internal set; }
/// <summary>
/// Gets or sets what type of activation this button will use when clicked. Defaults to Foreground.
@ -71,6 +91,272 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// </summary>
public string HintActionId { get; set; }
/// <summary>
/// Sets the text to display on the button.
/// </summary>
/// <param name="content">The text to display on the button.</param>
/// <returns>The current instance of the <see cref="ToastButton"/>.</returns>
public ToastButton SetContent(string content)
{
Content = content;
return this;
}
/// <summary>
/// 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="ToastButton"/></returns>
public ToastButton 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="ToastButton"/></returns>
#if WINRT
[Windows.Foundation.Metadata.DefaultOverload]
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
#endif
public ToastButton 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="ToastButton"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
#endif
public ToastButton 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="ToastButton"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
#endif
public ToastButton 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="ToastButton"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
#endif
public ToastButton 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="ToastButton"/></returns>
#if WINRT
[return: System.Runtime.InteropServices.WindowsRuntime.ReturnValueName("ToastButton")]
#endif
public ToastButton 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="ToastButton"/></returns>
public ToastButton AddArgument(string key, Enum value)
{
return AddArgumentHelper(key, ((int)(object)value).ToString());
}
#endif
private ToastButton AddArgumentHelper(string key, string value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (_usingCustomArguments)
{
throw new InvalidOperationException("You cannot use the AddArgument methods if you've set the Arguments property. Use the default ToastButton constructor instead.");
}
if (ActivationType == ToastActivationType.Protocol)
{
throw new InvalidOperationException("You cannot use the AddArgument methods when using protocol activation.");
}
bool alreadyExists = _arguments.ContainsKey(key);
_arguments[key] = value;
Arguments = alreadyExists ? SerializeArgumentsHelper(_arguments) : AddArgumentHelper(Arguments, 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 (string.IsNullOrEmpty(existing))
{
return pair;
}
else
{
return existing + ToastArguments.Separator + pair;
}
}
/// <summary>
/// Configures the button to launch the specified url when the button is clicked.
/// </summary>
/// <param name="protocol">The protocol to launch.</param>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetProtocolActivation(Uri protocol)
{
return SetProtocolActivation(protocol, default);
}
/// <summary>
/// Configures the button to launch the specified url when the button 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="ToastButton"/></returns>
public ToastButton SetProtocolActivation(Uri protocol, string targetApplicationPfn)
{
if (_arguments.Count > 0)
{
throw new InvalidOperationException("SetProtocolActivation cannot be used in conjunction with AddArgument");
}
Arguments = protocol.ToString();
ActivationType = ToastActivationType.Protocol;
if (targetApplicationPfn != null)
{
if (ActivationOptions == null)
{
ActivationOptions = new ToastActivationOptions();
}
ActivationOptions.ProtocolActivationTargetApplicationPfn = targetApplicationPfn;
}
return this;
}
/// <summary>
/// Configures the button to use background activation when the button is clicked.
/// </summary>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetBackgroundActivation()
{
ActivationType = ToastActivationType.Background;
return this;
}
/// <summary>
/// Sets the behavior that the toast should use when the user invokes this button. Desktop-only, supported in builds 16251 or higher. New in Fall Creators Update.
/// </summary>
/// <param name="afterActivationBehavior">The behavior that the toast should use when the user invokes this button.</param>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetAfterActivationBehavior(ToastAfterActivationBehavior afterActivationBehavior)
{
if (ActivationOptions == null)
{
ActivationOptions = new ToastActivationOptions();
}
ActivationOptions.AfterActivationBehavior = afterActivationBehavior;
return this;
}
/// <summary>
/// Sets an identifier used in telemetry to identify your category of action. This should be something like "Delete", "Reply", or "Archive". In the upcoming toast telemetry dashboard in Dev Center, you will be able to view how frequently your actions are being clicked.
/// </summary>
/// <param name="actionId">An identifier used in telemetry to identify your category of action.</param>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetHintActionId(string actionId)
{
HintActionId = actionId;
return this;
}
/// <summary>
/// Sets an optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).
/// </summary>
/// <param name="imageUri">An optional image icon for the button to display.</param>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetImageUri(Uri imageUri)
{
ImageUri = imageUri.ToString();
return this;
}
/// <summary>
/// Sets the 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.
/// </summary>
/// <param name="textBoxId">The ID of an existing <see cref="ToastTextBox"/>.</param>
/// <returns>The current instance of <see cref="ToastButton"/></returns>
public ToastButton SetTextBoxId(string textBoxId)
{
TextBoxId = textBoxId;
return this;
}
internal bool CanAddArguments()
{
return ActivationType != ToastActivationType.Protocol && !_usingCustomArguments;
}
internal bool ContainsArgument(string key)
{
return _arguments.ContainsKey(key);
}
internal Element_ToastAction ConvertToElement()
{
var el = new Element_ToastAction()

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

@ -272,6 +272,7 @@
<Content Include="Icons\More.png" />
<Content Include="Icons\Notifications.png" />
<Content Include="Icons\Services.png" />
<Content Include="SamplePages\ColorPicker\ColorPicker.png" />
<Content Include="SamplePages\TilesBrush\TilesBrush.png" />
<Content Include="SamplePages\Eyedropper\Eyedropper.png" />
<Content Include="SamplePages\OnDevice\OnDevice.png" />
@ -509,6 +510,12 @@
<Compile Include="SamplePages\AutoFocusBehavior\AutoFocusBehaviorPage.xaml.cs">
<DependentUpon>AutoFocusBehaviorPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\ColorPicker\ColorPickerButtonPage.xaml.cs">
<DependentUpon>ColorPickerButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\ColorPicker\ColorPickerPage.xaml.cs">
<DependentUpon>ColorPickerPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\EnumValuesExtension\EnumValuesExtensionPage.xaml.cs">
<DependentUpon>EnumValuesExtensionPage.xaml</DependentUpon>
</Compile>
@ -620,6 +627,8 @@
<Content Include="SamplePages\EnumValuesExtension\EnumValuesExtensionCode.bind" />
<Content Include="SamplePages\FocusBehavior\FocusBehaviorXaml.bind" />
<Content Include="SamplePages\AutoFocusBehavior\AutoFocusBehaviorXaml.bind" />
<Content Include="SamplePages\ColorPicker\ColorPickerXaml.bind" />
<Content Include="SamplePages\ColorPicker\ColorPickerButtonXaml.bind" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
@ -993,6 +1002,14 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="SamplePages\ColorPicker\ColorPickerButtonPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="SamplePages\ColorPicker\ColorPickerPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="SamplePages\EnumValuesExtension\EnumValuesExtensionPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@ -1471,12 +1488,10 @@
<Page Include="Styles\Custom\PivotHeaderItemUnderlineStyle.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Page>
<Page Include="Styles\Generic.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Page>
<Page Include="Styles\GithubIcon.xaml">
<SubType>Designer</SubType>
@ -1485,7 +1500,6 @@
<Page Include="Styles\Themes.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Page>
</ItemGroup>
<ItemGroup>

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

@ -4,7 +4,6 @@
using Microsoft.Toolkit.Uwp.Notifications;
using Windows.ApplicationModel.Background;
using Windows.UI.Notifications;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
@ -12,28 +11,9 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
public void Run(IBackgroundTaskInstance taskInstance)
{
// Create content of the toast notification
var toastContent = new ToastContent()
{
Scenario = ToastScenario.Default,
Visual = new ToastVisual
{
BindingGeneric = new ToastBindingGeneric
{
Children =
{
new AdaptiveText
{
Text = "New toast notification (BackgroundTaskHelper)."
}
}
}
}
};
// Create & show toast notification
var toastNotification = new ToastNotification(toastContent.GetXml());
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
new ToastContentBuilder()
.AddText("New toast notification (BackgroundTaskHelper).")
.Show();
}
}
}

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.3 KiB

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

@ -0,0 +1,15 @@
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ColorPickerButtonPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid Visibility="Collapsed">
<controls:ColorPickerButton />
<primitives:ColorPickerSlider />
</Grid>
</Page>

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

@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI.Controls;
using Windows.UI.Xaml.Controls;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
/// <summary>
/// A page that shows how to use the <see cref="UI.Controls.ColorPicker"/> control.
/// </summary>
public sealed partial class ColorPickerButtonPage : Page
{
public ColorPickerButtonPage()
{
this.InitializeComponent();
}
}
}

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

@ -0,0 +1,135 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<SolidColorBrush Color="{ThemeResource SystemChromeLowColor}" x:Key="SystemControlForegroundChromeLowBrush"/>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Example 1 -->
<StackPanel Grid.Row="0"
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Box-shaped spectrum <LineBreak />
Alpha channel disabled
</TextBlock>
</Border>
<controls:ColorPickerButton x:Name="ColorPickerButton1"
SelectedColor="Navy">
<controls:ColorPickerButton.ColorPickerStyle>
<Style TargetType="controls:ColorPicker">
<Setter Property="ColorSpectrumShape" Value="Box"/>
<Setter Property="IsAlphaEnabled" Value="False"/>
<Setter Property="IsHexInputVisible" Value="True"/>
</Style>
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
<!-- Example 2 -->
<StackPanel Grid.Row="1"
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Box-shaped spectrum <LineBreak />
Alpha channel enabled
</TextBlock>
</Border>
<controls:ColorPickerButton x:Name="ColorPickerButton2"
SelectedColor="Green">
<controls:ColorPickerButton.ColorPickerStyle>
<Style TargetType="controls:ColorPicker">
<Setter Property="ColorSpectrumShape" Value="Box"/>
<Setter Property="IsAlphaEnabled" Value="True"/>
<Setter Property="IsHexInputVisible" Value="False"/>
</Style>
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
<!-- Example 3 -->
<StackPanel Grid.Row="2"
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled
</TextBlock>
</Border>
<controls:ColorPickerButton x:Name="ColorPickerButton3"
SelectedColor="Transparent">
<controls:ColorPickerButton.ColorPickerStyle>
<Style TargetType="controls:ColorPicker">
<Setter Property="ColorSpectrumShape" Value="Ring"/>
<Setter Property="IsAlphaEnabled" Value="True"/>
<Setter Property="IsHexInputVisible" Value="True"/>
</Style>
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
<!-- Example 4 -->
<StackPanel Grid.Row="3"
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled <LineBreak />
Saturation+Value spectrum channels
</TextBlock>
</Border>
<controls:ColorPickerButton x:Name="ColorPickerButton4"
SelectedColor="Maroon">
<controls:ColorPickerButton.ColorPickerStyle>
<Style TargetType="controls:ColorPicker">
<Setter Property="ColorSpectrumShape" Value="Ring"/>
<Setter Property="ColorSpectrumComponents" Value="SaturationValue"/>
<Setter Property="IsAlphaEnabled" Value="True"/>
<Setter Property="IsHexInputVisible" Value="True"/>
</Style>
</controls:ColorPickerButton.ColorPickerStyle>
</controls:ColorPickerButton>
</StackPanel>
</Grid>
</Page>

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

@ -0,0 +1,17 @@
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.ColorPickerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:primitives="using:Microsoft.Toolkit.Uwp.UI.Controls.Primitives"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid
Visibility="Collapsed">
<controls:ColorPicker />
<primitives:ColorPickerSlider />
</Grid>
</Page>

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

@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI.Controls;
using Windows.UI.Xaml.Controls;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
/// <summary>
/// A page that shows how to use the <see cref="UI.Controls.ColorPicker"/> control.
/// </summary>
public sealed partial class ColorPickerPage : Page
{
public ColorPickerPage()
{
this.InitializeComponent();
}
}
}

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

@ -0,0 +1,89 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<SolidColorBrush Color="{ThemeResource SystemChromeLowColor}" x:Key="SystemControlForegroundChromeLowBrush"/>
</Page.Resources>
<ScrollViewer>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center"
Spacing="20">
<!-- Example 1 -->
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Box-shaped spectrum <LineBreak />
Alpha channel disabled
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPicker1"
Color="Navy"
ColorSpectrumShape="Box"
IsAlphaEnabled="False"
IsHexInputVisible="True" />
<!-- Example 2 -->
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Box-shaped spectrum <LineBreak />
Alpha channel enabled
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPicker2"
Color="Green"
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsHexInputVisible="False" />
<!-- Example 3 -->
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPickerButton3"
Color="Transparent"
ColorSpectrumShape="Ring"
IsAlphaEnabled="True"
IsHexInputVisible="True" />
<!-- Example 4 -->
<Border Background="{ThemeResource SystemChromeMediumColor}"
CornerRadius="4"
Height="100"
Width="300"
Padding="10">
<TextBlock TextAlignment="Center"
VerticalAlignment="Center">
Ring-shaped spectrum <LineBreak />
Alpha channel enabled <LineBreak />
Saturation+Value spectrum channels
</TextBlock>
</Border>
<controls:ColorPicker x:Name="ColorPickerButton4"
Color="Maroon"
ColorSpectrumShape="Ring"
ColorSpectrumComponents="SaturationValue"
IsAlphaEnabled="True"
IsHexInputVisible="True"/>
</StackPanel>
</ScrollViewer>
</Page>

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

@ -1,14 +1,10 @@
private void PopToast()
{
// Generate the toast notification content and pop the toast
ToastContent content = GenerateToastContent();
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(content.GetXml()));
}
public static ToastContent GenerateToastContent()
{
var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
.AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
new ToastContentBuilder()
.SetToastScenario(ToastScenario.Reminder)
.AddArgument("action", "viewEvent")
.AddArgument("eventId", 1983)
.AddText("Adaptive Tiles Meeting")
.AddText("Conf Room 2001 / Building 135")
.AddText("10:00 AM - 10:30 AM")
@ -18,7 +14,6 @@ public static ToastContent GenerateToastContent()
("240", "4 hours"),
("1440", "1 day"))
.AddButton(new ToastButtonSnooze() { SelectionBoxId = "snoozeTime" })
.AddButton(new ToastButtonDismiss());
return builder.Content;
}
.AddButton(new ToastButtonDismiss())
.Show();
}

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

@ -28,8 +28,10 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
public static ToastContent GenerateToastContent()
{
var builder = new ToastContentBuilder().SetToastScenario(ToastScenario.Reminder)
.AddToastActivationInfo("action=viewEvent&eventId=1983", ToastActivationType.Foreground)
var builder = new ToastContentBuilder()
.SetToastScenario(ToastScenario.Reminder)
.AddArgument("action", "viewEvent")
.AddArgument("eventId", 1983)
.AddText("Adaptive Tiles Meeting")
.AddText("Conf Room 2001 / Building 135")
.AddText("10:00 AM - 10:30 AM")
@ -54,7 +56,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
private void PopToast()
{
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
ToastNotificationManagerCompat.CreateToastNotifier().Show(new ToastNotification(_toastContent.GetXml()));
}
private void Initialize()

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