Merge branch 'master' into test-chop-imageex

This commit is contained in:
Kyaa Dost 2021-02-16 14:53:48 -08:00 коммит произвёл GitHub
Родитель 9d9f2532a4 4e54ec5f49
Коммит c2fc4ad5a7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
58 изменённых файлов: 1303 добавлений и 262 удалений

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

@ -327,4 +327,4 @@ dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializ
dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this.
dotnet_diagnostic.SA1000.severity = none # Hide warnings when using the new() expression from C# 9.
dotnet_diagnostic.SA1313.severity = none # Hide warnings for record parameters.
dotnet_diagnostic.SA1101.severity = none # Hide warnings when accessing properties without "this".
dotnet_diagnostic.SA1101.severity = none # Hide warnings when accessing properties without "this".

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

@ -91,7 +91,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public static MemoryOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new MemoryOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
get => new(0, ArrayPool<T>.Shared, AllocationMode.Default);
}
/// <summary>
@ -103,7 +103,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
public static MemoryOwner<T> Allocate(int size) => new(size, ArrayPool<T>.Shared, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
@ -115,7 +115,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool) => new MemoryOwner<T>(size, pool, AllocationMode.Default);
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool) => new(size, pool, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
@ -127,7 +127,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, mode);
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new(size, ArrayPool<T>.Shared, mode);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
@ -140,7 +140,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new MemoryOwner<T>(size, pool, mode);
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new(size, pool, mode);
/// <summary>
/// Gets the number of items in the current instance

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

@ -80,7 +80,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SpanOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
get => new(0, ArrayPool<T>.Shared, AllocationMode.Default);
}
/// <summary>
@ -92,7 +92,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
public static SpanOwner<T> Allocate(int size) => new(size, ArrayPool<T>.Shared, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
@ -104,7 +104,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool) => new SpanOwner<T>(size, pool, AllocationMode.Default);
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool) => new(size, pool, AllocationMode.Default);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
@ -116,7 +116,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, ArrayPool<T>.Shared, mode);
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new(size, ArrayPool<T>.Shared, mode);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
@ -129,7 +129,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new SpanOwner<T>(size, pool, mode);
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new(size, pool, mode);
/// <summary>
/// Gets the number of items in the current instance
@ -183,7 +183,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArraySegment<T> DangerousGetArray()
{
return new ArraySegment<T>(array!, 0, this.length);
return new(array!, 0, this.length);
}
/// <summary>

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

@ -137,7 +137,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
/// process. Since <see cref="StringPool"/> is thread-safe, the shared instance can be used
/// concurrently by multiple threads without the need for manual synchronization.
/// </remarks>
public static StringPool Shared { get; } = new StringPool();
public static StringPool Shared { get; } = new();
/// <summary>
/// Gets the total number of <see cref="string"/> that can be stored in the current instance.

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

@ -10,6 +10,8 @@ using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#if !SPAN_RUNTIME_SUPPORT
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
@ -76,6 +78,32 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length);
this.step = step;
}
/// <summary>
/// Creates a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="value">The reference to the first <typeparamref name="T"/> item to map.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
/// <returns>A <see cref="ReadOnlyRefEnumerable{T}"/> instance with the specified parameters.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the parameters are negative.</exception>
[Pure]
public static ReadOnlyRefEnumerable<T> DangerousCreate(in T value, int length, int step)
{
if (length < 0)
{
ThrowArgumentOutOfRangeExceptionForLength();
}
if (step < 0)
{
ThrowArgumentOutOfRangeExceptionForStep();
}
OverflowHelper.EnsureIsInNativeIntRange(length, 1, step);
return new ReadOnlyRefEnumerable<T>(in value, length, step);
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
@ -360,6 +388,22 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "length" parameter is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForLength()
{
throw new ArgumentOutOfRangeException("length");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "step" parameter is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForStep()
{
throw new ArgumentOutOfRangeException("step");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>

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

@ -10,7 +10,9 @@ using System.Runtime.InteropServices;
#endif
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
#if !SPAN_RUNTIME_SUPPORT
#if SPAN_RUNTIME_SUPPORT
using Microsoft.Toolkit.HighPerformance.Memory.Internals;
#else
using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
@ -64,6 +66,32 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
Span = MemoryMarshal.CreateSpan(ref reference, length);
Step = step;
}
/// <summary>
/// Creates a new instance of the <see cref="RefEnumerable{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="value">The reference to the first <typeparamref name="T"/> item to map.</param>
/// <param name="length">The number of items in the sequence.</param>
/// <param name="step">The distance between items in the sequence to enumerate.</param>
/// <returns>A <see cref="RefEnumerable{T}"/> instance with the specified parameters.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when one of the parameters are negative.</exception>
[Pure]
public static RefEnumerable<T> DangerousCreate(ref T value, int length, int step)
{
if (length < 0)
{
ThrowArgumentOutOfRangeExceptionForLength();
}
if (step < 0)
{
ThrowArgumentOutOfRangeExceptionForStep();
}
OverflowHelper.EnsureIsInNativeIntRange(length, 1, step);
return new RefEnumerable<T>(ref value, length, step);
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="RefEnumerable{T}"/> struct.
@ -453,6 +481,22 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
}
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "length" parameter is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForLength()
{
throw new ArgumentOutOfRangeException("length");
}
/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "step" parameter is invalid.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForStep()
{
throw new ArgumentOutOfRangeException("step");
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when the target span is too short.
/// </summary>

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

@ -146,7 +146,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this T[] array)
{
return new SpanEnumerable<T>(array);
return new(array);
}
/// <summary>
@ -172,7 +172,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator)
where T : IEquatable<T>
{
return new SpanTokenizer<T>(array, separator);
return new(array, separator);
}
/// <summary>

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

@ -207,7 +207,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,]? array)
{
return new Span2D<T>(array);
return new(array);
}
/// <summary>
@ -231,7 +231,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(array, row, column, height, width);
}
/// <summary>
@ -244,7 +244,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this T[,]? array)
{
return new Memory2D<T>(array);
return new(array);
}
/// <summary>
@ -268,7 +268,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(array, row, column, height, width);
}
#if SPAN_RUNTIME_SUPPORT

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

@ -234,7 +234,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this T[,,] array, int depth)
{
return new Span2D<T>(array, depth);
return new(array, depth);
}
/// <summary>
@ -252,7 +252,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this T[,,] array, int depth)
{
return new Memory2D<T>(array, depth);
return new(array, depth);
}
/// <summary>

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

@ -35,7 +35,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D<T> AsMemory2D<T>(this Memory<T> memory, int height, int width)
{
return new Memory2D<T>(memory, height, width);
return new(memory, height, width);
}
/// <summary>
@ -58,7 +58,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(memory, offset, height, width, pitch);
}
#endif

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

@ -38,7 +38,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlyMemory2D<T> AsMemory2D<T>(this ReadOnlyMemory<T> memory, int height, int width)
{
return new ReadOnlyMemory2D<T>(memory, height, width);
return new(memory, height, width);
}
/// <summary>
@ -61,7 +61,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(memory, offset, height, width, pitch);
}
#endif

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

@ -183,7 +183,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D<T> AsSpan2D<T>(this ReadOnlySpan<T> span, int height, int width)
{
return new ReadOnlySpan2D<T>(span, height, width);
return new(span, height, width);
}
/// <summary>
@ -206,7 +206,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(span, offset, height, width, pitch);
}
#endif
@ -313,7 +313,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this ReadOnlySpan<T> span)
{
return new ReadOnlySpanEnumerable<T>(span);
return new(span);
}
/// <summary>
@ -339,7 +339,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static ReadOnlySpanTokenizer<T> Tokenize<T>(this ReadOnlySpan<T> span, T separator)
where T : IEquatable<T>
{
return new ReadOnlySpanTokenizer<T>(span, separator);
return new(span, separator);
}
/// <summary>

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

@ -85,7 +85,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D<T> AsSpan2D<T>(this Span<T> span, int height, int width)
{
return new Span2D<T>(span, height, width);
return new(span, height, width);
}
/// <summary>
@ -108,7 +108,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[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);
return new(span, offset, height, width, pitch);
}
#endif
@ -214,7 +214,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable<T> Enumerate<T>(this Span<T> span)
{
return new SpanEnumerable<T>(span);
return new(span);
}
/// <summary>
@ -240,7 +240,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
public static SpanTokenizer<T> Tokenize<T>(this Span<T> span, T separator)
where T : IEquatable<T>
{
return new SpanTokenizer<T>(span, separator);
return new(span, separator);
}
/// <summary>

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

@ -32,7 +32,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe UnsafeLock Enter(SpinLock* spinLock)
{
return new UnsafeLock(spinLock);
return new(spinLock);
}
/// <summary>
@ -97,7 +97,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Lock Enter(ref this SpinLock spinLock)
{
return new Lock(ref spinLock);
return new(ref spinLock);
}
#else
/// <summary>
@ -123,7 +123,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Lock Enter(object owner, ref SpinLock spinLock)
{
return new Lock(owner, ref spinLock);
return new(owner, ref spinLock);
}
#endif

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

@ -121,7 +121,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<char> Enumerate(this string text)
{
return new ReadOnlySpanEnumerable<char>(text.AsSpan());
return new(text.AsSpan());
}
/// <summary>
@ -145,7 +145,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer<char> Tokenize(this string text, char separator)
{
return new ReadOnlySpanTokenizer<char>(text.AsSpan(), separator);
return new(text.AsSpan(), separator);
}
/// <summary>

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

@ -900,6 +900,6 @@ namespace Microsoft.Toolkit.HighPerformance
/// <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);
public static implicit operator Memory2D<T>(T[,]? array) => new(array);
}
}

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

@ -625,14 +625,14 @@ namespace Microsoft.Toolkit.HighPerformance
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);
return new ReadOnlySpan2D<T>(in 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);
return new ReadOnlySpan2D<T>(in r0, this.height, this.width, this.pitch);
}
#else
return new ReadOnlySpan2D<T>(this.instance, this.offset, this.height, this.width, this.pitch);
@ -913,7 +913,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// <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);
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new(array);
/// <summary>
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>

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

@ -38,7 +38,7 @@ namespace Microsoft.Toolkit.HighPerformance
ref T r1 = ref Unsafe.Add(ref r0, startIndex);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Width, 1);
return new ReadOnlyRefEnumerable<T>(in r1, Width, 1);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
@ -65,7 +65,7 @@ namespace Microsoft.Toolkit.HighPerformance
ref T r1 = ref Unsafe.Add(ref r0, (nint)(uint)column);
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlyRefEnumerable<T>(r1, Height, this.stride);
return new ReadOnlyRefEnumerable<T>(in r1, Height, this.stride);
#else
IntPtr offset = RuntimeHelpers.GetObjectDataOrReferenceByteOffset(this.instance, ref r1);
@ -81,7 +81,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
public Enumerator GetEnumerator() => new(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="ReadOnlySpan2D{T}"/> instance.

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

@ -478,7 +478,7 @@ namespace Microsoft.Toolkit.HighPerformance
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
return new ReadOnlySpan2D<T>(value, height, width, pitch);
return new ReadOnlySpan2D<T>(in value, height, width, pitch);
}
#endif
@ -837,7 +837,7 @@ namespace Microsoft.Toolkit.HighPerformance
#if SPAN_RUNTIME_SUPPORT
ref T r0 = ref this.span.DangerousGetReferenceAt(shift);
return new ReadOnlySpan2D<T>(r0, height, width, pitch);
return new ReadOnlySpan2D<T>(in r0, height, width, pitch);
#else
IntPtr offset = this.offset + (shift * (nint)(uint)Unsafe.SizeOf<T>());
@ -1012,7 +1012,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// Implicily converts a given 2D array into a <see cref="ReadOnlySpan2D{T}"/> instance.
/// </summary>
/// <param name="array">The input 2D array to convert.</param>
public static implicit operator ReadOnlySpan2D<T>(T[,]? array) => new ReadOnlySpan2D<T>(array);
public static implicit operator ReadOnlySpan2D<T>(T[,]? array) => new(array);
/// <summary>
/// Implicily converts a given <see cref="Span2D{T}"/> into a <see cref="ReadOnlySpan2D{T}"/> instance.
@ -1021,7 +1021,7 @@ namespace Microsoft.Toolkit.HighPerformance
public static implicit operator ReadOnlySpan2D<T>(Span2D<T> span)
{
#if SPAN_RUNTIME_SUPPORT
return new ReadOnlySpan2D<T>(span.DangerousGetReference(), span.Height, span.Width, span.Stride - span.Width);
return new ReadOnlySpan2D<T>(in span.DangerousGetReference(), span.Height, span.Width, span.Stride - span.Width);
#else
return new ReadOnlySpan2D<T>(span.Instance!, span.Offset, span.Height, span.Width, span.Stride - span.Width);
#endif

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

@ -81,7 +81,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// </returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
public Enumerator GetEnumerator() => new(this);
/// <summary>
/// Provides an enumerator for the elements of a <see cref="Span2D{T}"/> instance.

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

@ -1168,6 +1168,6 @@ namespace Microsoft.Toolkit.HighPerformance
/// Implicily converts a given 2D array into a <see cref="Span2D{T}"/> instance.
/// </summary>
/// <param name="array">The input 2D array to convert.</param>
public static implicit operator Span2D<T>(T[,]? array) => new Span2D<T>(array);
public static implicit operator Span2D<T>(T[,]? array) => new(array);
}
}

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

@ -92,7 +92,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator NullableReadOnlyRef<T>(Ref<T> reference)
{
return new NullableReadOnlyRef<T>(reference.Span);
return new(reference.Span);
}
/// <summary>
@ -102,7 +102,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator NullableReadOnlyRef<T>(ReadOnlyRef<T> reference)
{
return new NullableReadOnlyRef<T>(reference.Span);
return new(reference.Span);
}
/// <summary>
@ -112,7 +112,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator NullableReadOnlyRef<T>(NullableRef<T> reference)
{
return new NullableReadOnlyRef<T>(reference.Span);
return new(reference.Span);
}
/// <summary>

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

@ -96,7 +96,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator NullableRef<T>(Ref<T> reference)
{
return new NullableRef<T>(reference.Span);
return new(reference.Span);
}
/// <summary>

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

@ -42,7 +42,7 @@ namespace Microsoft.Toolkit.HighPerformance
/// <param name="pointer">The pointer to the target value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ReadOnlyRef(void* pointer)
: this(Unsafe.AsRef<T>(pointer))
: this(in Unsafe.AsRef<T>(pointer))
{
}
@ -62,7 +62,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRef<T>(Ref<T> reference)
{
return new ReadOnlyRef<T>(reference.Value);
return new(in reference.Value);
}
#else
/// <summary>
@ -117,7 +117,7 @@ namespace Microsoft.Toolkit.HighPerformance
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlyRef<T>(Ref<T> reference)
{
return new ReadOnlyRef<T>(reference.Owner, reference.Offset);
return new(reference.Owner, reference.Offset);
}
#endif

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

@ -50,7 +50,7 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
public static ArrayOwner Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new ArrayOwner(Array.Empty<byte>(), 0, 0);
get => new(Array.Empty<byte>(), 0, 0);
}
/// <inheritdoc/>

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

@ -340,7 +340,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
// instance. This will result in no further allocations after the first time this method is called for a given
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
}
/// <summary>
@ -362,7 +362,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, Action<Task?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, callback, propertyName);
}
/// <summary>
@ -401,7 +401,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, static _ => { }, propertyName);
}
/// <summary>
@ -424,7 +424,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, Action<Task<T>?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, callback, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new(), newValue, callback, propertyName);
}
/// <summary>

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

@ -116,7 +116,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName)
{
var message = new PropertyChangedMessage<T>(this, propertyName, oldValue, newValue);
PropertyChangedMessage<T> message = new(this, propertyName, oldValue, newValue);
Messenger.Send(message);
}

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

@ -9,6 +9,8 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.ComponentModel
@ -19,15 +21,25 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </summary>
public abstract class ObservableValidator : ObservableObject, INotifyDataErrorInfo
{
/// <summary>
/// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track compiled delegates to validate entities.
/// </summary>
private static readonly ConditionalWeakTable<Type, Action<object>> EntityValidatorMap = new();
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="HasErrors"/>.
/// </summary>
private static readonly PropertyChangedEventArgs HasErrorsChangedEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
private static readonly PropertyChangedEventArgs HasErrorsChangedEventArgs = new(nameof(HasErrors));
/// <summary>
/// The <see cref="ValidationContext"/> instance currenty in use.
/// </summary>
private readonly ValidationContext validationContext;
/// <summary>
/// The <see cref="Dictionary{TKey,TValue}"/> instance used to store previous validation results.
/// </summary>
private readonly Dictionary<string, List<ValidationResult>> errors = new Dictionary<string, List<ValidationResult>>();
private readonly Dictionary<string, List<ValidationResult>> errors = new();
/// <summary>
/// Indicates the total number of properties with errors (not total errors).
@ -39,6 +51,59 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// <inheritdoc/>
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ObservableValidator"/> class.
/// This constructor will create a new <see cref="ValidationContext"/> that will
/// be used to validate all properties, which will reference the current instance
/// and no additional services or validation properties and settings.
/// </summary>
protected ObservableValidator()
{
this.validationContext = new ValidationContext(this);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObservableValidator"/> class.
/// This constructor will create a new <see cref="ValidationContext"/> that will
/// be used to validate all properties, which will reference the current instance.
/// </summary>
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
protected ObservableValidator(IDictionary<object, object?> items)
{
this.validationContext = new ValidationContext(this, items);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObservableValidator"/> class.
/// This constructor will create a new <see cref="ValidationContext"/> that will
/// be used to validate all properties, which will reference the current instance.
/// </summary>
/// <param name="serviceProvider">An <see cref="IServiceProvider"/> instance to make available during validation.</param>
/// <param name="items">A set of key/value pairs to make available to consumers.</param>
protected ObservableValidator(IServiceProvider serviceProvider, IDictionary<object, object?> items)
{
this.validationContext = new ValidationContext(this, serviceProvider, items);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObservableValidator"/> class.
/// This constructor will store the input <see cref="ValidationContext"/> instance,
/// and it will use it to validate all properties for the current viewmodel.
/// </summary>
/// <param name="validationContext">
/// The <see cref="ValidationContext"/> instance to use to validate properties.
/// <para>
/// This instance will be passed to all <see cref="Validator.TryValidateObject(object, ValidationContext, ICollection{ValidationResult})"/>
/// calls executed by the current viewmodel, and its <see cref="ValidationContext.MemberName"/> property will be updated every time
/// before the call is made to set the name of the property being validated. The property name will not be reset after that, so the
/// value of <see cref="ValidationContext.MemberName"/> will always indicate the name of the last property that was validated, if any.
/// </para>
/// </param>
protected ObservableValidator(ValidationContext validationContext)
{
this.validationContext = validationContext;
}
/// <inheritdoc/>
public bool HasErrors => this.totalErrors > 0;
@ -326,18 +391,46 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
}
/// <inheritdoc/>
[Pure]
public IEnumerable GetErrors(string? propertyName)
/// <summary>
/// Clears the validation errors for a specified property or for the entire entity.
/// </summary>
/// <param name="propertyName">
/// The name of the property to clear validation errors for.
/// If a <see langword="null"/> or empty name is used, all entity-level errors will be cleared.
/// </param>
protected void ClearErrors(string? propertyName = null)
{
// Entity-level errors when the target property is null or empty
// Clear entity-level errors when the target property is null or empty
if (string.IsNullOrEmpty(propertyName))
{
return this.GetAllErrors();
ClearAllErrors();
}
else
{
ClearErrorsForProperty(propertyName!);
}
}
/// <inheritdoc cref="INotifyDataErrorInfo.GetErrors(string)"/>
[Pure]
public IEnumerable<ValidationResult> GetErrors(string? propertyName = null)
{
// Get entity-level errors when the target property is null or empty
if (string.IsNullOrEmpty(propertyName))
{
// Local function to gather all the entity-level errors
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
IEnumerable<ValidationResult> GetAllErrors()
{
return this.errors.Values.SelectMany(static errors => errors);
}
return GetAllErrors();
}
// Property-level errors, if any
if (this.errors.TryGetValue(propertyName!, out List<ValidationResult> errors))
if (this.errors.TryGetValue(propertyName!, out List<ValidationResult>? errors))
{
return errors;
}
@ -350,24 +443,75 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
return Array.Empty<ValidationResult>();
}
/// <summary>
/// Implements the logic for entity-level errors gathering for <see cref="GetErrors"/>.
/// </summary>
/// <returns>An <see cref="IEnumerable"/> instance with all the errors in <see cref="errors"/>.</returns>
/// <inheritdoc/>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
IEnumerable INotifyDataErrorInfo.GetErrors(string? propertyName) => GetErrors(propertyName);
/// <summary>
/// Validates all the properties in the current instance and updates all the tracked errors.
/// If any changes are detected, the <see cref="ErrorsChanged"/> event will be raised.
/// </summary>
/// <remarks>
/// Only public instance properties (excluding custom indexers) that have at least one
/// <see cref="ValidationAttribute"/> applied to them will be validated. All other
/// members in the current instance will be ignored. None of the processed properties
/// will be modified - they will only be used to retrieve their values and validate them.
/// </remarks>
protected void ValidateAllProperties()
{
return this.errors.Values.SelectMany(errors => errors);
static Action<object> GetValidationAction(Type type)
{
// MyViewModel inst0 = (MyViewModel)arg0;
ParameterExpression arg0 = Expression.Parameter(typeof(object));
UnaryExpression inst0 = Expression.Convert(arg0, type);
// Get a reference to ValidateProperty(object, string)
MethodInfo validateMethod = typeof(ObservableValidator).GetMethod(nameof(ValidateProperty), BindingFlags.Instance | BindingFlags.NonPublic)!;
// We want a single compiled LINQ expression that validates all properties in the
// actual type of the executing viewmodel at once. We do this by creating a block
// expression with the unrolled invocations of all properties to validate.
// Essentially, the body will contain the following code:
// ===============================================================================
// {
// inst0.ValidateProperty(inst0.Property0, nameof(MyViewModel.Property0));
// inst0.ValidateProperty(inst0.Property1, nameof(MyViewModel.Property1));
// ...
// }
// ===============================================================================
// We also add an explicit object conversion to represent boxing, if a given property
// is a value type. It will just be a no-op if the value is a reference type already.
// Note that this generated code is technically accessing a protected method from
// ObservableValidator externally, but that is fine because IL doesn't really have
// a concept of member visibility, that's purely a C# build-time feature.
BlockExpression body = Expression.Block(
from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
where property.GetIndexParameters().Length == 0 &&
property.GetCustomAttributes<ValidationAttribute>(true).Any()
let getter = property.GetMethod
where getter is not null
select Expression.Call(inst0, validateMethod, new Expression[]
{
Expression.Convert(Expression.Call(inst0, getter), typeof(object)),
Expression.Constant(property.Name)
}));
return Expression.Lambda<Action<object>>(body, arg0).Compile();
}
// Get or compute the cached list of properties to validate. Here we're using a static lambda to ensure the
// delegate is cached by the C# compiler, see the related issue at https://github.com/dotnet/roslyn/issues/5835.
EntityValidatorMap.GetValue(GetType(), static t => GetValidationAction(t))(this);
}
/// <summary>
/// Validates a property with a specified name and a given input value.
/// If any changes are detected, the <see cref="ErrorsChanged"/> event will be raised.
/// </summary>
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
private void ValidateProperty(object? value, string? propertyName)
protected void ValidateProperty(object? value, [CallerMemberName] string? propertyName = null)
{
if (propertyName is null)
{
@ -380,7 +524,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
// If the property isn't present in the dictionary, add it now to avoid allocations.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
propertyErrors = new();
this.errors.Add(propertyName!, propertyErrors);
}
@ -396,10 +540,9 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
}
// Validate the property, by adding new errors to the existing list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
propertyErrors);
this.validationContext.MemberName = propertyName;
bool isValid = Validator.TryValidateProperty(value, this.validationContext, propertyErrors);
// Update the shared counter for the number of errors, and raise the
// property changed event if necessary. We decrement the number of total
@ -457,20 +600,19 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
// Add the cached errors list for later use.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
propertyErrors = new();
this.errors.Add(propertyName!, propertyErrors);
}
bool hasErrors = propertyErrors.Count > 0;
List<ValidationResult> localErrors = new List<ValidationResult>();
List<ValidationResult> localErrors = new();
// Validate the property, by adding new errors to the local list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
localErrors);
this.validationContext.MemberName = propertyName;
bool isValid = Validator.TryValidateProperty(value, this.validationContext, localErrors);
// We only modify the state if the property is valid and it wasn't so before. In this case, we
// clear the cached list of errors (which is visible to consumers) and raise the necessary events.
@ -493,6 +635,59 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
return isValid;
}
/// <summary>
/// Clears all the current errors for the entire entity.
/// </summary>
private void ClearAllErrors()
{
if (this.totalErrors == 0)
{
return;
}
// Clear the errors for all properties with at least one error, and raise the
// ErrorsChanged event for those properties. Other properties will be ignored.
foreach (var propertyInfo in this.errors)
{
bool hasErrors = propertyInfo.Value.Count > 0;
propertyInfo.Value.Clear();
if (hasErrors)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyInfo.Key));
}
}
this.totalErrors = 0;
OnPropertyChanged(HasErrorsChangedEventArgs);
}
/// <summary>
/// Clears all the current errors for a target property.
/// </summary>
/// <param name="propertyName">The name of the property to clear errors for.</param>
private void ClearErrorsForProperty(string propertyName)
{
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors) ||
propertyErrors.Count == 0)
{
return;
}
propertyErrors.Clear();
this.totalErrors--;
if (this.totalErrors == 0)
{
OnPropertyChanged(HasErrorsChangedEventArgs);
}
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
#pragma warning disable SA1204
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when a property name given as input is <see langword="null"/>.

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

@ -47,7 +47,7 @@ namespace Microsoft.Toolkit.Mvvm.DependencyInjection
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();
public static Ioc Default { get; } = new();
/// <summary>
/// The <see cref="IServiceProvider"/> instance to use, if initialized.
@ -134,7 +134,7 @@ namespace Microsoft.Toolkit.Mvvm.DependencyInjection
{
IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null);
if (!(oldServices is null))
if (oldServices is not null)
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}

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

@ -22,17 +22,17 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="CanBeCanceled"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new PropertyChangedEventArgs(nameof(CanBeCanceled));
internal static readonly PropertyChangedEventArgs CanBeCanceledChangedEventArgs = new(nameof(CanBeCanceled));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsCancellationRequested"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new PropertyChangedEventArgs(nameof(IsCancellationRequested));
internal static readonly PropertyChangedEventArgs IsCancellationRequestedChangedEventArgs = new(nameof(IsCancellationRequested));
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IsRunning"/>.
/// </summary>
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new PropertyChangedEventArgs(nameof(IsRunning));
internal static readonly PropertyChangedEventArgs IsRunningChangedEventArgs = new(nameof(IsRunning));
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
@ -122,7 +122,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
public bool CanBeCanceled => this.cancelableExecute is not null && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -155,7 +155,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
if (CanExecute(parameter))
{
// Non cancelable command delegate
if (!(this.execute is null))
if (this.execute is not null)
{
return ExecutionTask = this.execute();
}
@ -163,7 +163,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();
OnPropertyChanged(IsCancellationRequestedChangedEventArgs);

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

@ -19,17 +19,17 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Func<T, Task>? execute;
private readonly Func<T?, Task>? execute;
/// <summary>
/// The cancelable <see cref="Func{T1,T2,TResult}"/> to invoke when <see cref="Execute(object?)"/> is used.
/// </summary>
private readonly Func<T, CancellationToken, Task>? cancelableExecute;
private readonly Func<T?, CancellationToken, Task>? cancelableExecute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
private readonly Predicate<T?>? canExecute;
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
@ -44,7 +44,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, Task> execute)
public AsyncRelayCommand(Func<T?, Task> execute)
{
this.execute = execute;
}
@ -54,7 +54,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
@ -65,7 +65,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, Task> execute, Func<T, bool> canExecute)
public AsyncRelayCommand(Func<T?, Task> execute, Predicate<T?> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
@ -77,7 +77,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Func<T, bool> canExecute)
public AsyncRelayCommand(Func<T?, CancellationToken, Task> cancelableExecute, Predicate<T?> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
@ -106,7 +106,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null) && IsRunning;
public bool CanBeCanceled => this.cancelableExecute is not null && IsRunning;
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
@ -122,7 +122,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T parameter)
public bool CanExecute(T? parameter)
{
return this.canExecute?.Invoke(parameter) != false;
}
@ -131,19 +131,18 @@ namespace Microsoft.Toolkit.Mvvm.Input
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(object? parameter)
{
if (typeof(T).IsValueType &&
parameter is null &&
this.canExecute is null)
if (default(T) is not null &&
parameter is null)
{
return true;
return false;
}
return CanExecute((T)parameter!);
return CanExecute((T?)parameter);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T parameter)
public void Execute(T? parameter)
{
ExecuteAsync(parameter);
}
@ -151,16 +150,16 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
public void Execute(object? parameter)
{
ExecuteAsync((T)parameter!);
ExecuteAsync((T?)parameter);
}
/// <inheritdoc/>
public Task ExecuteAsync(T parameter)
public Task ExecuteAsync(T? parameter)
{
if (CanExecute(parameter))
{
// Non cancelable command delegate
if (!(this.execute is null))
if (this.execute is not null)
{
return ExecutionTask = this.execute(parameter);
}
@ -168,7 +167,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
CancellationTokenSource cancellationTokenSource = this.cancellationTokenSource = new();
OnPropertyChanged(AsyncRelayCommand.IsCancellationRequestedChangedEventArgs);
@ -182,7 +181,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
public Task ExecuteAsync(object? parameter)
{
return ExecuteAsync((T)parameter!);
return ExecuteAsync((T?)parameter);
}
/// <inheritdoc/>

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

@ -18,6 +18,6 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
/// <param name="parameter">The input parameter.</param>
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
Task ExecuteAsync(T parameter);
Task ExecuteAsync(T? parameter);
}
}

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

@ -18,13 +18,13 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="parameter">The input parameter.</param>
/// <returns>Whether or not the current command can be executed.</returns>
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
bool CanExecute(T parameter);
bool CanExecute(T? parameter);
/// <summary>
/// Provides a strongly-typed variant of <see cref="ICommand.Execute(object)"/>.
/// </summary>
/// <param name="parameter">The input parameter.</param>
/// <remarks>Use this overload to avoid boxing, if <typeparamref name="T"/> is a value type.</remarks>
void Execute(T parameter);
void Execute(T? parameter);
}
}

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

@ -24,12 +24,12 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Action"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Action<T> execute;
private readonly Action<T?> execute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
private readonly Predicate<T?>? canExecute;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
@ -43,7 +43,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// nullable <see cref="object"/> parameter, it is recommended that if <typeparamref name="T"/> is a reference type,
/// you should always declare it as nullable, and to always perform checks within <paramref name="execute"/>.
/// </remarks>
public RelayCommand(Action<T> execute)
public RelayCommand(Action<T?> execute)
{
this.execute = execute;
}
@ -54,7 +54,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
public RelayCommand(Action<T?> execute, Predicate<T?> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
@ -68,7 +68,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T parameter)
public bool CanExecute(T? parameter)
{
return this.canExecute?.Invoke(parameter) != false;
}
@ -76,19 +76,18 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
public bool CanExecute(object? parameter)
{
if (typeof(T).IsValueType &&
parameter is null &&
this.canExecute is null)
if (default(T) is not null &&
parameter is null)
{
return true;
return false;
}
return CanExecute((T)parameter!);
return CanExecute((T?)parameter);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T parameter)
public void Execute(T? parameter)
{
if (CanExecute(parameter))
{
@ -99,7 +98,7 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <inheritdoc/>
public void Execute(object? parameter)
{
Execute((T)parameter!);
Execute((T?)parameter);
}
}
}

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

@ -47,8 +47,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <summary>
/// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track the preloaded registration actions for each recipient.
/// </summary>
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods
= new ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]>();
public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]> RegistrationMethods = new();
}
/// <summary>
@ -139,7 +138,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// For more info on this, see the related issue at https://github.com/dotnet/roslyn/issues/5835.
Action<IMessenger, object, TToken>[] registrationActions = DiscoveredRecipients<TToken>.RegistrationMethods.GetValue(
recipient.GetType(),
t => LoadRegistrationMethodsForType(t));
static t => LoadRegistrationMethodsForType(t));
foreach (Action<IMessenger, object, TToken> registrationAction in registrationActions)
{
@ -158,7 +157,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
where TMessage : class
{
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, static (r, m) => r.Receive(m));
}
/// <summary>
@ -175,7 +174,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TMessage : class
where TToken : IEquatable<TToken>
{
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, static (r, m) => r.Receive(m));
}
/// <summary>

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

@ -334,7 +334,7 @@ namespace Microsoft.Collections.Extensions
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this);
public Enumerator GetEnumerator() => new(this);
/// <summary>
/// Enumerator for <see cref="DictionarySlim{TKey,TValue}"/>.

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

@ -22,13 +22,13 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages
/// operations that can be executed in parallel, or <see cref="Func{T,TResult}"/> instances, which can be used so that multiple
/// asynchronous operations are only started sequentially from <see cref="GetAsyncEnumerator"/> and do not overlap in time.
/// </summary>
private readonly List<(Task<T>?, Func<CancellationToken, Task<T>>?)> responses = new List<(Task<T>?, Func<CancellationToken, Task<T>>?)>();
private readonly List<(Task<T>?, Func<CancellationToken, Task<T>>?)> responses = new();
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance used to link the token passed to
/// <see cref="GetAsyncEnumerator"/> and the one passed to all subscribers to the message.
/// </summary>
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly CancellationTokenSource cancellationTokenSource = new();
/// <summary>
/// Gets the <see cref="System.Threading.CancellationToken"/> instance that will be linked to the
@ -102,7 +102,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages
cancellationToken.Register(this.cancellationTokenSource.Cancel);
}
List<T> results = new List<T>(this.responses.Count);
List<T> results = new(this.responses.Count);
await foreach (var response in this.WithCancellation(cancellationToken))
{
@ -129,7 +129,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages
yield break;
}
if (!(task is null))
if (task is not null)
{
yield return await task.ConfigureAwait(false);
}

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

@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages
/// <typeparam name="T">The type of request to make.</typeparam>
public class CollectionRequestMessage<T> : IEnumerable<T>
{
private readonly List<T> responses = new List<T>();
private readonly List<T> responses = new();
/// <summary>
/// Gets the message responses.

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

@ -71,7 +71,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// so that all the existing handlers can be removed without having to dynamically create
/// the generic types for the containers of the various dictionaries mapping the handlers.
/// </remarks>
private readonly DictionarySlim<Recipient, HashSet<IMapping>> recipientsMap = new DictionarySlim<Recipient, HashSet<IMapping>>();
private readonly DictionarySlim<Recipient, HashSet<IMapping>> recipientsMap = new();
/// <summary>
/// The <see cref="Mapping{TMessage,TToken}"/> instance for types combination.
@ -81,12 +81,12 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// Each method relies on <see cref="GetOrAddMapping{TMessage,TToken}"/> to get the type-safe instance
/// of the <see cref="Mapping{TMessage,TToken}"/> class for each pair of generic arguments in use.
/// </remarks>
private readonly DictionarySlim<Type2, IMapping> typesMap = new DictionarySlim<Type2, IMapping>();
private readonly DictionarySlim<Type2, IMapping> typesMap = new();
/// <summary>
/// Gets the default <see cref="StrongReferenceMessenger"/> instance.
/// </summary>
public static StrongReferenceMessenger Default { get; } = new StrongReferenceMessenger();
public static StrongReferenceMessenger Default { get; } = new();
/// <inheritdoc/>
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
@ -100,7 +100,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
return false;
}
var key = new Recipient(recipient);
Recipient key = new(recipient);
return mapping!.ContainsKey(key);
}
@ -116,7 +116,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
{
// Get the <TMessage, TToken> registration list for this recipient
Mapping<TMessage, TToken> mapping = GetOrAddMapping<TMessage, TToken>();
var key = new Recipient(recipient);
Recipient key = new(recipient);
ref DictionarySlim<TToken, object>? map = ref mapping.GetOrAddValueRef(key);
map ??= new DictionarySlim<TToken, object>();
@ -124,7 +124,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// Add the new registration entry
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
if (!(registeredHandler is null))
if (registeredHandler is not null)
{
ThrowInvalidOperationExceptionForDuplicateRegistration();
}
@ -147,7 +147,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
lock (this.recipientsMap)
{
// If the recipient has no registered messages at all, ignore
var key = new Recipient(recipient);
Recipient key = new(recipient);
if (!this.recipientsMap.TryGetValue(key, out HashSet<IMapping>? set))
{
@ -196,7 +196,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
Monitor.Enter(this.recipientsMap, ref lockTaken);
// Get the shared set of mappings for the recipient, if present
var key = new Recipient(recipient);
Recipient key = new(recipient);
if (!this.recipientsMap.TryGetValue(key, out HashSet<IMapping>? set))
{
@ -273,7 +273,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
// Remove references to avoid leaks coming from the shared memory pool.
// We manually create a span and clear it as a small optimization, as
// arrays rented from the pool can be larger than the requested size.
if (!(maps is null))
if (maps is not null)
{
maps.AsSpan(0, i).Clear();
@ -295,7 +295,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
return;
}
var key = new Recipient(recipient);
Recipient key = new(recipient);
if (!mapping!.TryGetValue(key, out DictionarySlim<TToken, object>? dictionary))
{
@ -443,7 +443,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TMessage : class
where TToken : IEquatable<TToken>
{
var key = new Type2(typeof(TMessage), typeof(TToken));
Type2 key = new(typeof(TMessage), typeof(TToken));
if (this.typesMap.TryGetValue(key, out IMapping? target))
{
@ -472,7 +472,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
where TMessage : class
where TToken : IEquatable<TToken>
{
var key = new Type2(typeof(TMessage), typeof(TToken));
Type2 key = new(typeof(TMessage), typeof(TToken));
ref IMapping? target = ref this.typesMap.GetOrAddValueRef(key);
target ??= new Mapping<TMessage, TToken>();

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

@ -46,12 +46,12 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <summary>
/// The map of currently registered recipients for all message types.
/// </summary>
private readonly DictionarySlim<Type2, RecipientsTable> recipientsMap = new DictionarySlim<Type2, RecipientsTable>();
private readonly DictionarySlim<Type2, RecipientsTable> recipientsMap = new();
/// <summary>
/// Gets the default <see cref="WeakReferenceMessenger"/> instance.
/// </summary>
public static WeakReferenceMessenger Default { get; } = new WeakReferenceMessenger();
public static WeakReferenceMessenger Default { get; } = new();
/// <inheritdoc/>
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
@ -60,14 +60,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
{
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
Type2 type2 = new(typeof(TMessage), typeof(TToken));
// Get the conditional table associated with the target recipient, for the current pair
// of token and message types. If it exists, check if there is a matching token.
return
this.recipientsMap.TryGetValue(type2, out RecipientsTable? table) &&
table!.TryGetValue(recipient, out IDictionarySlim? mapping) &&
Unsafe.As<DictionarySlim<TToken, object>>(mapping).ContainsKey(token);
Unsafe.As<DictionarySlim<TToken, object>>(mapping)!.ContainsKey(token);
}
}
@ -79,7 +79,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
{
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
Type2 type2 = new(typeof(TMessage), typeof(TToken));
// Get the conditional table for the pair of type arguments, or create it if it doesn't exist
ref RecipientsTable? mapping = ref this.recipientsMap.GetOrAddValueRef(type2);
@ -87,12 +87,12 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
mapping ??= new RecipientsTable();
// Get or create the handlers dictionary for the target recipient
var map = Unsafe.As<DictionarySlim<TToken, object>>(mapping.GetValue(recipient, _ => new DictionarySlim<TToken, object>()));
var map = Unsafe.As<DictionarySlim<TToken, object>>(mapping.GetValue(recipient, static _ => new DictionarySlim<TToken, object>()));
// Add the new registration entry
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
if (!(registeredHandler is null))
if (registeredHandler is not null)
{
ThrowInvalidOperationExceptionForDuplicateRegistration();
}
@ -133,9 +133,9 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
while (enumerator.MoveNext())
{
if (enumerator.Key.TToken == typeof(TToken) &&
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
enumerator.Value.TryGetValue(recipient, out IDictionarySlim? mapping))
{
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
Unsafe.As<DictionarySlim<TToken, object>>(mapping)!.TryRemove(token, out _);
}
}
}
@ -148,7 +148,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
{
lock (this.recipientsMap)
{
var type2 = new Type2(typeof(TMessage), typeof(TToken));
Type2 type2 = new(typeof(TMessage), typeof(TToken));
var enumerator = this.recipientsMap.GetEnumerator();
// Traverse all the existing token and message pairs matching the current type
@ -156,9 +156,9 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
while (enumerator.MoveNext())
{
if (enumerator.Key.Equals(type2) &&
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
enumerator.Value.TryGetValue(recipient, out IDictionarySlim? mapping))
{
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
Unsafe.As<DictionarySlim<TToken, object>>(mapping)!.TryRemove(token, out _);
}
}
}
@ -174,7 +174,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
lock (this.recipientsMap)
{
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
Type2 type2 = new(typeof(TMessage), typeof(TToken));
// Try to get the target table
if (!this.recipientsMap.TryGetValue(type2, out RecipientsTable? table))
@ -302,25 +302,16 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
/// <summary>
/// The underlying <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/> instance.
/// </summary>
private readonly System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue> table;
private readonly System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue> table = new();
/// <summary>
/// A supporting linked list to store keys in <see cref="table"/>. This is needed to expose
/// the ability to enumerate existing keys when there is no support for that in the BCL.
/// </summary>
private readonly LinkedList<WeakReference<TKey>> keys;
/// <summary>
/// Initializes a new instance of the <see cref="ConditionalWeakTable{TKey, TValue}"/> class.
/// </summary>
public ConditionalWeakTable()
{
this.table = new System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>();
this.keys = new LinkedList<WeakReference<TKey>>();
}
private readonly LinkedList<WeakReference<TKey>> keys = new();
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
public bool TryGetValue(TKey key, out TValue value)
public bool TryGetValue(TKey key, out TValue? value)
{
return this.table.TryGetValue(key, out value);
}
@ -355,25 +346,92 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
}
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
for (LinkedListNode<WeakReference<TKey>>? node = this.keys.First; !(node is null);)
{
LinkedListNode<WeakReference<TKey>>? next = node.Next;
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(this);
// Get the key and value for the current node
if (node.Value.TryGetTarget(out TKey? target) &&
this.table.TryGetValue(target!, out TValue value))
/// <summary>
/// A custom enumerator that traverses items in a <see cref="ConditionalWeakTable{TKey, TValue}"/> instance.
/// </summary>
public ref struct Enumerator
{
/// <summary>
/// The owner <see cref="ConditionalWeakTable{TKey, TValue}"/> instance for the enumerator.
/// </summary>
private readonly ConditionalWeakTable<TKey, TValue> owner;
/// <summary>
/// The current <see cref="LinkedListNode{T}"/>, if any.
/// </summary>
private LinkedListNode<WeakReference<TKey>>? node;
/// <summary>
/// The current <see cref="KeyValuePair{TKey, TValue}"/> to return.
/// </summary>
private KeyValuePair<TKey, TValue> current;
/// <summary>
/// Indicates whether or not <see cref="MoveNext"/> has been called at least once.
/// </summary>
private bool isFirstMoveNextPending;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="owner">The owner <see cref="ConditionalWeakTable{TKey, TValue}"/> instance for the enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(ConditionalWeakTable<TKey, TValue> owner)
{
this.owner = owner;
this.node = null;
this.current = default;
this.isFirstMoveNextPending = true;
}
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
public bool MoveNext()
{
LinkedListNode<WeakReference<TKey>>? node;
if (!isFirstMoveNextPending)
{
yield return new KeyValuePair<TKey, TValue>(target, value);
node = this.node!.Next;
}
else
{
// If the current key has been collected, trim the list
this.keys.Remove(node);
node = this.owner.keys.First;
this.isFirstMoveNextPending = false;
}
node = next;
while (node is not null)
{
// Get the key and value for the current node
if (node.Value.TryGetTarget(out TKey? target) &&
this.owner.table.TryGetValue(target!, out TValue? value))
{
this.node = node;
this.current = new KeyValuePair<TKey, TValue>(target, value);
return true;
}
else
{
// If the current key has been collected, trim the list
this.owner.keys.Remove(node);
}
node = node.Next;
}
return false;
}
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
public readonly KeyValuePair<TKey, TValue> Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.current;
}
}
}

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

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Title>Windows Community Toolkit MVVM Toolkit</Title>
@ -10,8 +10,11 @@
This package includes a .NET Standard MVVM library with helpers such as:
- ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.
- ObservableRecipient: a base class for observable objects with support for the IMessenger service.
- ObservableValidator: a base class for objects implementing the INotifyDataErrorInfo interface.
- RelayCommand: a simple delegate command implementing the ICommand interface.
- Messenger: a messaging system to exchange messages through different loosely-coupled objects.
- AsyncRelayCommand: a delegate command supporting asynchronous operations and cancellation.
- WeakReferenceMessenger: a messaging system to exchange messages through different loosely-coupled objects.
- StrongReferenceMessenger: a high-performance messaging system that trades weak references for speed.
- Ioc: a helper class to configure dependency injection service containers.
</Description>
<PackageTags>UWP Toolkit Windows MVVM MVVMToolkit observable Ioc dependency injection services extensions helpers</PackageTags>
@ -19,17 +22,15 @@
<!-- .NET Standard 2.0 doesn't have the Span<T> and IAsyncEnumerable<T> types -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
</Project>

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

@ -355,25 +355,20 @@ namespace Microsoft.Toolkit.Uwp.Notifications
bool? silent = default)
#endif
{
if (!src.IsFile)
{
throw new ArgumentException(nameof(src), "Audio Source has to be a file.");
}
Content.Audio = new ToastAudio();
Content.Audio.Src = src;
var audio = new ToastAudio();
audio.Src = src;
if (loop != default)
{
Content.Audio.Loop = loop.Value;
audio.Loop = loop.Value;
}
if (silent != default)
{
Content.Audio.Silent = silent.Value;
audio.Silent = silent.Value;
}
return this;
return AddAudio(audio);
}
/// <summary>
@ -383,6 +378,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
public ToastContentBuilder AddAudio(ToastAudio audio)
{
if (audio.Src != null && !audio.Src.IsFile && audio.Src.Scheme != "ms-appx" && audio.Src.Scheme != "ms-winsoundevent")
{
throw new InvalidOperationException("Audio Source must either be a ms-appx file, absolute file, or ms-winsoundevent.");
}
Content.Audio = audio;
return this;
}

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

@ -29,7 +29,7 @@
"Type": "CarouselPage",
"Subcategory": "Layout",
"About": "Presents items in a carousel control. It reacts to changes in the layout as well as the content so it can adapt to different form factors automatically.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/Carousel",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Carousel",
"XamlCodeFile": "CarouselCode.bind",
"Icon": "/SamplePages/Carousel/Carousel.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/Carousel.md"
@ -39,7 +39,7 @@
"Type": "ColorPickerPage",
"Subcategory": "Input",
"About": "An improved color picker control providing more options to select colors.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/ColorPicker",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker",
"XamlCodeFile": "ColorPickerXaml.bind",
"Icon": "/SamplePages/ColorPicker/ColorPicker.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPicker.md"
@ -49,7 +49,7 @@
"Type": "ColorPickerButtonPage",
"Subcategory": "Input",
"About": "A color picker within a flyout opened by pressing a dropdown button containing the selected color.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/ColorPicker",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/ColorPicker",
"XamlCodeFile": "/SamplePages/ColorPicker/ColorPickerButtonXaml.bind",
"Icon": "/SamplePages/ColorPicker/ColorPicker.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ColorPickerButton.md"
@ -79,7 +79,7 @@
"Type": "RangeSelectorPage",
"Subcategory": "Input",
"About": "The RangeSelector is a \"double slider\" control for range values.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/RangeSelector",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/RangeSelector",
"XamlCodeFile": "RangeSelectorCode.bind",
"Icon": "/SamplePages/RangeSelector/RangeSelector.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/RangeSelector.md"
@ -99,7 +99,7 @@
"Type": "ListDetailsViewPage",
"Subcategory": "Layout",
"About": "The ListDetailsView control allows the user to implement the List/Details design pattern.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/ListDetailsView",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/ListDetailsView",
"XamlCodeFile": "ListDetailsView.bind",
"CodeFile": "ListDetailsViewCode.bind",
"Icon": "/SamplePages/ListDetailsView/ListDetailsView.png",
@ -121,7 +121,7 @@
"Type": "RadialGaugePage",
"Subcategory": "Status and Info",
"About": "The radial gauge displays a value within a range, using a needle on a circular face.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/RadialGauge",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/RadialGauge",
"XamlCodeFile": "RadialGaugeCode.bind",
"Icon": "/SamplePages/RadialGauge/RadialGauge.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/RadialGauge.md"
@ -151,7 +151,7 @@
"Type": "BladePage",
"Subcategory": "Layout",
"About": "BladeView provides a horizontal collection of blades for master-detail scenarios. The control is based on the experience demonstrated by the Azure Portal.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/BladeView",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/BladeView",
"XamlCodeFile": "BladeCode.bind",
"Icon": "/SamplePages/BladeView/BladeView.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/BladeView.md"
@ -170,7 +170,7 @@
"Type": "GridSplitterPage",
"Subcategory": "Layout",
"About": "GridSplitter represents the control that redistributes space between columns or rows of a Grid control.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/GridSplitter",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/GridSplitter",
"XamlCodeFile": "GridSplitter.bind",
"Icon": "/SamplePages/GridSplitter/GridSplitter.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/GridSplitter.md"
@ -201,7 +201,7 @@
"Type": "ExpanderPage",
"Subcategory": "Layout",
"About": "Expander control allows user to show/hide content based on a boolean state.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/Expander",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/Expander",
"XamlCodeFile": "ExpanderXaml.bind",
"Icon": "/SamplePages/Expander/Expander.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/Expander.md"
@ -231,7 +231,7 @@
"Type": "WrapLayoutPage",
"Subcategory": "Layout - ItemsRepeater",
"About": "The WrapLayout virtualizes child elements in sequential position from left to right, breaking content to the next line at the edge of the containing box.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/WrapLayout",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/WrapLayout",
"XamlCodeFile": "WrapLayout.bind",
"Icon": "/SamplePages/WrapLayout/WrapLayout.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/layout/WrapLayout.md"
@ -241,7 +241,7 @@
"Type": "OrbitViewPage",
"Subcategory": "Layout",
"About": "The OrbitView Control positions items in a circle around a center element and supports orbits and anchors.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/OrbitView",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/OrbitView",
"XamlCodeFile": "OrbitViewXaml.bind",
"Icon": "/SamplePages/OrbitView/OrbitView.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/OrbitView.md"
@ -284,7 +284,7 @@
"Type": "HeaderedContentControlPage",
"Subcategory": "Layout",
"About": "Allows content to be displayed with a specified header.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/HeaderedContentControl",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/HeaderedContentControl",
"XamlCodeFile": "HeaderedContentControlXaml.bind",
"Icon": "/SamplePages/HeaderedContentControl/HeaderedContentControl.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/HeaderedContentControl.md"
@ -294,7 +294,7 @@
"Type": "HeaderedItemsControlPage",
"Subcategory": "Layout",
"About": "Allows items to be displayed with a specified header.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/HeaderedItemsControl",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/HeaderedItemsControl",
"XamlCodeFile": "HeaderedItemsControlXaml.bind",
"Icon": "/SamplePages/HeaderedItemsControl/HeaderedItemsControl.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/HeaderedItemsControl.md"
@ -314,7 +314,7 @@
"Type": "StaggeredLayoutPage",
"Subcategory": "Layout - ItemsRepeater",
"About": "The StaggeredLayout virtualizes items in a column approach where an item will be added to whichever column has used the least amount of space.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/StaggeredLayout",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/StaggeredLayout",
"XamlCodeFile": "StaggeredLayout.bind",
"Icon": "/SamplePages/StaggeredLayout/StaggeredLayout.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/layout/StaggeredLayout.md"
@ -324,7 +324,7 @@
"Type": "LayoutTransformControlPage",
"Subcategory": "Layout",
"About": "Control that implements support for transformations as if applied by LayoutTransform.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/LayoutTransformControl",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Layout/LayoutTransformControl",
"XamlCodeFile": "LayoutTransformControlXaml.bind",
"Icon": "/SamplePages/LayoutTransformControl/LayoutTransformControl.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/LayoutTransformControl.md"
@ -359,7 +359,7 @@
"Type": "RemoteDevicePickerControlPage",
"Subcategory": "Input",
"About": "Remote Device Picker Control for Project Rome.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/RemoteDevicePicker",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/RemoteDevicePicker",
"CodeFile": "RemoteDevicePickerCode.bind",
"Icon": "/SamplePages/RemoteDevicePicker/RemoteDevicePicker.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/RemoteDevicePicker.md"
@ -449,7 +449,7 @@
"Type": "TokenizingTextBoxPage",
"Subcategory": "Input",
"About": "A text input control that makes suggestions and keeps track of data token items",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/TokenizingTextBox",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox",
"CodeFile": "TokenizingTextBoxCode.bind",
"XamlCodeFile": "TokenizingTextBoxXaml.bind",
"Icon": "/SamplePages/TokenizingTextBox/TokenizingTextBox.png",
@ -459,7 +459,7 @@
"Name": "TabbedCommandBar",
"Subcategory": "Menus and Toolbars",
"About": "A control for displaying multiple CommandBars in the same space, like Microsoft Office's ribbon.",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/TabbedCommandBar",
"CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls.Core/TabbedCommandBar",
"XamlCodeFile": "/SamplePages/TabbedCommandBar/TabbedCommandBar.bind",
"Icon": "/SamplePages/TabbedCommandBar/TabbedCommandBar.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/TabbedCommandBar.md"

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

@ -200,7 +200,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Extensions
}
/// <summary>
/// Finds the logical parent element with the given name or returns null.
/// Finds the logical parent element with the given name or returns null. Note: Parent may only be set when the control is added to the VisualTree.
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
/// </summary>
/// <param name="element">Child element.</param>
/// <param name="name">Name of the control to find.</param>
@ -226,7 +227,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Extensions
}
/// <summary>
/// Find first logical parent control of a specified type.
/// Find first logical parent control of a specified type. Note: Parent may only be set when the control is added to the VisualTree.
/// <seealso href="https://docs.microsoft.com/uwp/api/windows.ui.xaml.frameworkelement.parent#remarks"/>
/// </summary>
/// <typeparam name="T">Type to search for.</typeparam>
/// <param name="element">Child element.</param>

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

@ -16,17 +16,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Helpers
/// <summary>
/// Provides a method to execute code after the rendering pass is completed.
/// <seealso href="https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp#L399"/>
/// <seealso href="https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/"/>
/// </summary>
/// <param name="action">Action to be executed after render pass</param>
/// <param name="options"><see cref="TaskCreationOptions"/> for how to handle async calls with <see cref="TaskCompletionSource{TResult}"/>.</param>
/// <returns>Awaitable Task</returns>
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action)
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action, TaskCreationOptions? options = null)
{
if (action is null)
{
ThrowArgumentNullException();
}
var taskCompletionSource = new TaskCompletionSource<bool>();
var taskCompletionSource = options.HasValue ? new TaskCompletionSource<bool>(options.Value)
: new TaskCompletionSource<bool>();
try
{

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

@ -0,0 +1,53 @@
// 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.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Enumerables
{
[TestClass]
public class Test_ReadOnlyRefEnumerable
{
[TestCategory("ReadOnlyRefEnumerable")]
[TestMethod]
[DataRow(1, 1, new[] { 1 })]
[DataRow(4, 1, new[] { 1, 2, 3, 4 })]
[DataRow(4, 4, new[] { 1, 5, 9, 13 })]
[DataRow(4, 5, new[] { 1, 6, 11, 16 })]
public void Test_ReadOnlyRefEnumerable_DangerousCreate_Ok(int length, int step, int[] values)
{
Span<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};
ReadOnlyRefEnumerable<int> enumerable = ReadOnlyRefEnumerable<int>.DangerousCreate(in data[0], length, step);
int[] result = enumerable.ToArray();
CollectionAssert.AreEqual(result, values);
}
[TestCategory("ReadOnlyRefEnumerable")]
[TestMethod]
[DataRow(-44, 10)]
[DataRow(10, -14)]
[DataRow(-32, -1)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int step)
{
_ = ReadOnlyRefEnumerable<int>.DangerousCreate(in Unsafe.NullRef<int>(), length, step);
}
}
}
#endif

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

@ -0,0 +1,53 @@
// 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.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTests.HighPerformance.Enumerables
{
[TestClass]
public class Test_RefEnumerable
{
[TestCategory("RefEnumerable")]
[TestMethod]
[DataRow(1, 1, new[] { 1 })]
[DataRow(4, 1, new[] { 1, 2, 3, 4 })]
[DataRow(4, 4, new[] { 1, 5, 9, 13 })]
[DataRow(4, 5, new[] { 1, 6, 11, 16 })]
public void Test_RefEnumerable_DangerousCreate_Ok(int length, int step, int[] values)
{
Span<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};
RefEnumerable<int> enumerable = RefEnumerable<int>.DangerousCreate(ref data[0], length, step);
int[] result = enumerable.ToArray();
CollectionAssert.AreEqual(result, values);
}
[TestCategory("RefEnumerable")]
[TestMethod]
[DataRow(-44, 10)]
[DataRow(10, -14)]
[DataRow(-32, -1)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step)
{
_ = RefEnumerable<int>.DangerousCreate(ref Unsafe.NullRef<int>(), length, step);
}
}
}
#endif

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

@ -16,6 +16,8 @@
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_StringPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Buffers\TrackingArrayPool{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enumerables\Test_ReadOnlyRefEnumerable{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Enumerables\Test_RefEnumerable{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.3D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.1D.cs" />

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

@ -16,7 +16,7 @@
<!-- Workaround for the .NET Core 2.1 binary not resolving the Unsafe assembly properly -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

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

@ -548,6 +548,72 @@ namespace UnitTests.Notifications
Assert.AreEqual(testToastAudioSilent, builder.Content.Audio.Silent);
}
[TestMethod]
public void AddAudioTest_WithMsWinSoundEvent_ReturnSelfWithCustomAudioAdded()
{
// Arrange
Uri testAudioUriSrc = new Uri("ms-winsoundevent:Notification.Reminder");
// Act
ToastContentBuilder builder = new ToastContentBuilder();
ToastContentBuilder anotherReference = builder.AddAudio(testAudioUriSrc);
// Assert
Assert.AreSame(builder, anotherReference);
Assert.AreEqual(testAudioUriSrc.OriginalString, builder.Content.Audio.Src.OriginalString);
}
[TestMethod]
public void AddAudioTest_WithMsAppx_ReturnSelfWithCustomAudioAdded()
{
// Arrange
Uri testAudioUriSrc = new Uri("ms-appx:///Assets/Audio.mp3");
// Act
ToastContentBuilder builder = new ToastContentBuilder();
ToastContentBuilder anotherReference = builder.AddAudio(testAudioUriSrc);
// Assert
Assert.AreSame(builder, anotherReference);
Assert.AreEqual(testAudioUriSrc.OriginalString, builder.Content.Audio.Src.OriginalString);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void AddAudioTest_WithInvalidMsUri_ThrowException()
{
// Arrange
Uri testAudioUriSrc = new Uri("ms-doesntexist:Notification.Reminder");
// Act
ToastContentBuilder builder = new ToastContentBuilder();
builder.AddAudio(testAudioUriSrc);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void AddAudioTest_WithInvalidAppDataUri_ThrowException()
{
// Arrange (ms-appdata isn't currently supported)
Uri testAudioUriSrc = new Uri("ms-appdata:///local/Sound.mp3");
// Act
ToastContentBuilder builder = new ToastContentBuilder();
builder.AddAudio(testAudioUriSrc);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void AddAudioTest_WithInvalidHttpUri_ThrowException()
{
// Arrange
Uri testAudioUriSrc = new Uri("https://myaudio.com/song.mp3");
// Act
ToastContentBuilder builder = new ToastContentBuilder();
builder.AddAudio(testAudioUriSrc);
}
[TestMethod]
public void AddAudioTest_WithAudioObject_ReturnSelfWithCustomAudioAdded()
{

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

@ -125,8 +125,8 @@ namespace UnitTests.Mvvm
return Task.CompletedTask;
});
// Special case for null value types
Assert.IsTrue(command.CanExecute(null));
Assert.IsFalse(command.CanExecute(null));
Assert.ThrowsException<NullReferenceException>(() => command.Execute(null));
command = new AsyncRelayCommand<int>(
i =>
@ -135,7 +135,8 @@ namespace UnitTests.Mvvm
return Task.CompletedTask;
}, i => i > 0);
Assert.ThrowsException<NullReferenceException>(() => command.CanExecute(null));
Assert.IsFalse(command.CanExecute(null));
Assert.ThrowsException<NullReferenceException>(() => command.Execute(null));
}
}
}

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

@ -2,10 +2,12 @@
// 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.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -88,33 +90,33 @@ namespace UnitTests.Mvvm
{
var model = new Person();
Assert.AreEqual(model.GetErrors(null).Cast<object>().Count(), 0);
Assert.AreEqual(model.GetErrors(string.Empty).Cast<object>().Count(), 0);
Assert.AreEqual(model.GetErrors("ThereIsntAPropertyWithThisName").Cast<object>().Count(), 0);
Assert.AreEqual(model.GetErrors(nameof(Person.Name)).Cast<object>().Count(), 0);
Assert.AreEqual(model.GetErrors(null).Count(), 0);
Assert.AreEqual(model.GetErrors(string.Empty).Count(), 0);
Assert.AreEqual(model.GetErrors("ThereIsntAPropertyWithThisName").Count(), 0);
Assert.AreEqual(model.GetErrors(nameof(Person.Name)).Count(), 0);
model.Name = "Foo";
var errors = model.GetErrors(nameof(Person.Name)).Cast<ValidationResult>().ToArray();
var errors = model.GetErrors(nameof(Person.Name)).ToArray();
Assert.AreEqual(errors.Length, 1);
Assert.AreEqual(errors[0].MemberNames.First(), nameof(Person.Name));
Assert.AreEqual(model.GetErrors("ThereIsntAPropertyWithThisName").Cast<object>().Count(), 0);
Assert.AreEqual(model.GetErrors("ThereIsntAPropertyWithThisName").Count(), 0);
errors = model.GetErrors(null).Cast<ValidationResult>().ToArray();
errors = model.GetErrors(null).ToArray();
Assert.AreEqual(errors.Length, 1);
Assert.AreEqual(errors[0].MemberNames.First(), nameof(Person.Name));
errors = model.GetErrors(string.Empty).Cast<ValidationResult>().ToArray();
errors = model.GetErrors(string.Empty).ToArray();
Assert.AreEqual(errors.Length, 1);
Assert.AreEqual(errors[0].MemberNames.First(), nameof(Person.Name));
model.Age = -1;
errors = model.GetErrors(null).Cast<ValidationResult>().ToArray();
errors = model.GetErrors(null).ToArray();
Assert.AreEqual(errors.Length, 2);
Assert.IsTrue(errors.Any(e => e.MemberNames.First().Equals(nameof(Person.Name))));
@ -122,7 +124,7 @@ namespace UnitTests.Mvvm
model.Age = 26;
errors = model.GetErrors(null).Cast<ValidationResult>().ToArray();
errors = model.GetErrors(null).ToArray();
Assert.AreEqual(errors.Length, 1);
Assert.IsTrue(errors.Any(e => e.MemberNames.First().Equals(nameof(Person.Name))));
@ -145,11 +147,11 @@ namespace UnitTests.Mvvm
if (isValid)
{
Assert.IsTrue(!model.GetErrors(nameof(Person.Name)).Cast<object>().Any());
Assert.IsTrue(!model.GetErrors(nameof(Person.Name)).Any());
}
else
{
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Cast<object>().Any());
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Any());
}
}
@ -196,7 +198,7 @@ namespace UnitTests.Mvvm
// Errors should now be present
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 1);
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Cast<ValidationResult>().Any());
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Any());
Assert.IsTrue(model.HasErrors);
// Trying to set a correct property should clear the errors
@ -207,6 +209,189 @@ namespace UnitTests.Mvvm
Assert.AreEqual(model.Name, "This is fine");
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_ValidateProperty()
{
var model = new ComparableModel();
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
// Set a correct value for both properties, first A then B
model.A = 42;
model.B = 30;
Assert.AreEqual(events.Count, 0);
Assert.IsFalse(model.HasErrors);
// Make B greater than A, hence invalidating A
model.B = 50;
Assert.AreEqual(events.Count, 1);
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
Assert.IsTrue(model.HasErrors);
events.Clear();
// Make A greater than B, hence making it valid again
model.A = 51;
Assert.AreEqual(events.Count, 1);
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 0);
Assert.IsFalse(model.HasErrors);
events.Clear();
// Make A smaller than B, hence invalidating it
model.A = 49;
Assert.AreEqual(events.Count, 1);
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 1);
Assert.IsTrue(model.HasErrors);
events.Clear();
// Lower B, hence making A valid again
model.B = 20;
Assert.AreEqual(events.Count, 1);
Assert.AreEqual(events.Last().PropertyName, nameof(ComparableModel.A));
Assert.AreEqual(model.GetErrors(nameof(ComparableModel.A)).Count(), 0);
Assert.IsFalse(model.HasErrors);
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_ClearErrors()
{
var model = new Person();
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
model.Age = -2;
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(model.GetErrors(nameof(Person.Age)).Any());
model.ClearErrors(nameof(Person.Age));
Assert.IsFalse(model.HasErrors);
Assert.IsTrue(events.Count == 2);
model.Age = 200;
model.Name = "Bo";
events.Clear();
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(model.GetErrors(nameof(Person.Age)).Any());
Assert.IsTrue(model.GetErrors(nameof(Person.Name)).Any());
model.ClearErrors(null);
Assert.IsFalse(model.HasErrors);
Assert.IsFalse(model.GetErrors(nameof(Person.Age)).Any());
Assert.IsFalse(model.GetErrors(nameof(Person.Name)).Any());
Assert.IsTrue(events.Count == 2);
Assert.IsTrue(events[0].PropertyName == nameof(Person.Age));
Assert.IsTrue(events[1].PropertyName == nameof(Person.Name));
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_ValidateAllProperties()
{
var model = new PersonWithDeferredValidation();
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
model.ValidateAllProperties();
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 2);
// Note: we can't use an index here because the order used to return properties
// from reflection APIs is an implementation detail and might change at any time.
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
events.Clear();
model.Name = "James";
model.Age = 42;
model.ValidateAllProperties();
Assert.IsFalse(model.HasErrors);
Assert.IsTrue(events.Count == 2);
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Name)));
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
events.Clear();
model.Age = -10;
model.ValidateAllProperties();
Assert.IsTrue(model.HasErrors);
Assert.IsTrue(events.Count == 1);
Assert.IsTrue(events.Any(e => e.PropertyName == nameof(Person.Age)));
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_CustomValidation()
{
var items = new Dictionary<object, object> { [nameof(CustomValidationModel.A)] = 42 };
var model = new CustomValidationModel(items);
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
model.A = 10;
Assert.IsFalse(model.HasErrors);
Assert.AreEqual(events.Count, 0);
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableValidator_CustomValidationWithInjectedService()
{
var model = new ValidationWithServiceModel(new FancyService());
var events = new List<DataErrorsChangedEventArgs>();
model.ErrorsChanged += (s, e) => events.Add(e);
model.Name = "This is a valid name";
Assert.IsFalse(model.HasErrors);
Assert.AreEqual(events.Count, 0);
model.Name = "This is invalid238!!";
Assert.IsTrue(model.HasErrors);
Assert.AreEqual(events.Count, 1);
Assert.AreEqual(events[0].PropertyName, nameof(ValidationWithServiceModel.Name));
Assert.AreEqual(model.GetErrors(nameof(ValidationWithServiceModel.Name)).ToArray().Length, 1);
model.Name = "This is valid but it is too long so the validation will fail anyway";
Assert.IsTrue(model.HasErrors);
Assert.AreEqual(events.Count, 2);
Assert.AreEqual(events[1].PropertyName, nameof(ValidationWithServiceModel.Name));
Assert.AreEqual(model.GetErrors(nameof(ValidationWithServiceModel.Name)).ToArray().Length, 1);
model.Name = "This is both too long and it also contains invalid characters, a real disaster!";
Assert.IsTrue(model.HasErrors);
Assert.AreEqual(events.Count, 3);
Assert.AreEqual(events[2].PropertyName, nameof(ValidationWithServiceModel.Name));
Assert.AreEqual(model.GetErrors(nameof(ValidationWithServiceModel.Name)).ToArray().Length, 2);
}
public class Person : ObservableValidator
{
private string name;
@ -233,6 +418,166 @@ namespace UnitTests.Mvvm
get => this.age;
set => SetProperty(ref this.age, value, true);
}
public new void ClearErrors(string propertyName)
{
base.ClearErrors(propertyName);
}
}
public class PersonWithDeferredValidation : ObservableValidator
{
[MinLength(4)]
[MaxLength(20)]
[Required]
public string Name { get; set; }
[Range(18, 100)]
public int Age { get; set; }
// Extra property with no validation
public float Foo { get; set; } = float.NaN;
public new void ValidateAllProperties()
{
base.ValidateAllProperties();
}
}
/// <summary>
/// Test model for linked properties, to test <see cref="ObservableValidator.ValidateProperty(object?, string?)"/> instance.
/// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3665 for the original request for this feature.
/// </summary>
public class ComparableModel : ObservableValidator
{
private int a;
[Range(10, 100)]
[GreaterThan(nameof(B))]
public int A
{
get => this.a;
set => SetProperty(ref this.a, value, true);
}
private int b;
[Range(20, 80)]
public int B
{
get => this.b;
set
{
SetProperty(ref this.b, value, true);
ValidateProperty(A, nameof(A));
}
}
}
public sealed class GreaterThanAttribute : ValidationAttribute
{
public GreaterThanAttribute(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
object
instance = validationContext.ObjectInstance,
otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);
if (((IComparable)value).CompareTo(otherValue) > 0)
{
return ValidationResult.Success;
}
return new ValidationResult("The current value is smaller than the other one");
}
}
/// <summary>
/// Test model for custom validation properties.
/// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3729 for the original request for this feature.
/// </summary>
public class CustomValidationModel : ObservableValidator
{
public CustomValidationModel(IDictionary<object, object> items)
: base(items)
{
}
private int a;
[CustomValidation(typeof(CustomValidationModel), nameof(ValidateA))]
public int A
{
get => this.a;
set => SetProperty(ref this.a, value, true);
}
public static ValidationResult ValidateA(int x, ValidationContext context)
{
Assert.AreEqual(context.MemberName, nameof(A));
if ((int)context.Items[nameof(A)] == 42)
{
return ValidationResult.Success;
}
return new ValidationResult("Missing the magic number");
}
}
public interface IFancyService
{
bool Validate(string name);
}
public class FancyService : IFancyService
{
public bool Validate(string name)
{
return Regex.IsMatch(name, @"^[A-Za-z ]+$");
}
}
/// <summary>
/// Test model for custom validation with an injected service.
/// See https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3750 for the original request for this feature.
/// </summary>
public class ValidationWithServiceModel : ObservableValidator
{
private readonly IFancyService service;
public ValidationWithServiceModel(IFancyService service)
{
this.service = service;
}
private string name;
[MaxLength(25, ErrorMessage = "The name is too long")]
[CustomValidation(typeof(ValidationWithServiceModel), nameof(ValidateName))]
public string Name
{
get => this.name;
set => SetProperty(ref this.name, value, true);
}
public static ValidationResult ValidateName(string name, ValidationContext context)
{
bool isValid = ((ValidationWithServiceModel)context.ObjectInstance).service.Validate(name);
if (isValid)
{
return ValidationResult.Success;
}
return new ValidationResult("The name contains invalid characters");
}
}
}
}

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

@ -74,12 +74,13 @@ namespace UnitTests.Mvvm
var command = new RelayCommand<int>(i => n = i);
// Special case for null value types
Assert.IsTrue(command.CanExecute(null));
Assert.IsFalse(command.CanExecute(null));
Assert.ThrowsException<NullReferenceException>(() => command.Execute(null));
command = new RelayCommand<int>(i => n = i, i => i > 0);
Assert.ThrowsException<NullReferenceException>(() => command.CanExecute(null));
Assert.IsFalse(command.CanExecute(null));
Assert.ThrowsException<NullReferenceException>(() => command.Execute(null));
}
}
}

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

@ -0,0 +1,66 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
namespace UnitTests.Extensions
{
[TestClass]
public class Test_LogicalTreeExtensions : VisualUITestBase
{
[TestCategory("LogicalTree")]
[TestMethod]
public async Task Test_LogicalTree_FindParent_Exists()
{
await App.DispatcherQueue.EnqueueAsync(async () =>
{
var treeRoot = XamlReader.Load(@"<Page
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<Grid>
<Grid> <!-- Target -->
<Border/>
<Border>
<TextBlock/> <!-- Starting Point -->
</Border>
</Grid>
</Grid>
</Page>") as Page;
// Test Setup
Assert.IsNotNull(treeRoot, "XAML Failed to Load");
// Initialize Visual Tree
await SetTestContentAsync(treeRoot);
var outerGrid = treeRoot.Content as Grid;
Assert.IsNotNull(outerGrid, "Couldn't find Page content.");
var targetGrid = outerGrid.Children.FirstOrDefault() as Grid;
Assert.IsNotNull(targetGrid, "Couldn't find Target Grid");
Assert.AreEqual(2, targetGrid.Children.Count, "Grid doesn't have right number of children.");
var secondBorder = targetGrid.Children[1] as Border;
Assert.IsNotNull(secondBorder, "Border not found.");
var startingPoint = secondBorder.Child as FrameworkElement;
Assert.IsNotNull(startingPoint, "Could not find starting element.");
// Main Test
var grid = startingPoint.FindParent<Grid>();
Assert.IsNotNull(grid, "Expected to find Grid");
Assert.AreEqual(targetGrid, grid, "Grid didn't match expected.");
});
}
}
}

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

@ -79,7 +79,7 @@ namespace UnitTests.UI.Controls
[TestMethod]
public async Task Test_TextToolbar_Localization_Override_Fr()
{
await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () =>
await App.DispatcherQueue.EnqueueAsync(async () =>
{
// Just double-check we've got the right environment setup in our tests.
CollectionAssert.AreEquivalent(new string[] { "en-US", "fr" }, ApplicationLanguages.ManifestLanguages.ToArray(), "Missing locales for test");

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

@ -3,11 +3,14 @@
// See the LICENSE file in the project root for more information.
using System;
using UnitTests.Extensions;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Core;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace UnitTests
@ -17,6 +20,31 @@ namespace UnitTests
/// </summary>
public partial class App : Application
{
// Holder for test content to abstract Window.Current.Content
public static FrameworkElement ContentRoot
{
get
{
var rootFrame = Window.Current.Content as Frame;
return rootFrame.Content as FrameworkElement;
}
set
{
var rootFrame = Window.Current.Content as Frame;
rootFrame.Content = value;
}
}
// Abstract CoreApplication.MainView.DispatcherQueue
public static DispatcherQueue DispatcherQueue
{
get
{
return CoreApplication.MainView.DispatcherQueue;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
@ -50,7 +78,10 @@ namespace UnitTests
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame = new Frame()
{
CacheSize = 0 // Prevent any test pages from being cached
};
rootFrame.NavigationFailed += OnNavigationFailed;

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

@ -18,7 +18,7 @@
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<Target Name="Pack">
</Target>
@ -114,10 +114,10 @@
<Version>6.2.10</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>2.1.0</Version>
<Version>2.1.2</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>2.1.0</Version>
<Version>2.1.2</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.3</Version>
@ -150,6 +150,7 @@
<Compile Include="Extensions\Test_FontIconExtensionMarkupExtension.cs" />
<Compile Include="Extensions\Test_EnumValuesExtension.cs" />
<Compile Include="Extensions\Test_NullableBoolMarkupExtension.cs" />
<Compile Include="Extensions\Test_LogicalTreeExtensions.cs" />
<Compile Include="Geometry\Test_CanvasPathGeometry.cs" />
<Compile Include="Geometry\Test_RegexFactory.cs" />
<Compile Include="Geometry\Test_Utils.cs" />
@ -208,6 +209,7 @@
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
<Compile Include="VisualUITestBase.cs" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">

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

@ -0,0 +1,77 @@
// 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.Extensions;
using Microsoft.Toolkit.Uwp.UI.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml;
namespace UnitTests
{
/// <summary>
/// Base class to be used in API tests which require UI layout or rendering to occur first.
/// For more E2E scenarios or testing components for user interation, see integration test suite instead.
/// Use this class when an API needs direct access to test functions of the UI itself in more simplistic scenarios (i.e. visual tree helpers).
/// </summary>
public class VisualUITestBase
{
/// <summary>
/// Sets the content of the test app to a simple <see cref="FrameworkElement"/> to load into the visual tree.
/// Waits for that element to be loaded and rendered before returning.
/// </summary>
/// <param name="content">Content to set in test app.</param>
/// <returns>When UI is loaded.</returns>
protected Task SetTestContentAsync(FrameworkElement content)
{
return App.DispatcherQueue.EnqueueAsync(() =>
{
var taskCompletionSource = new TaskCompletionSource<bool>();
async void Callback(object sender, RoutedEventArgs args)
{
content.Loaded -= Callback;
// Wait for first Render pass
await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
taskCompletionSource.SetResult(true);
}
// Going to wait for our original content to unload
content.Loaded += Callback;
// Trigger that now
try
{
App.ContentRoot = content;
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}
return taskCompletionSource.Task;
});
}
[TestCleanup]
public async Task Cleanup()
{
var taskCompletionSource = new TaskCompletionSource<bool>();
await App.DispatcherQueue.EnqueueAsync(() =>
{
// Going to wait for our original content to unload
App.ContentRoot.Unloaded += (_, _) => taskCompletionSource.SetResult(true);
// Trigger that now
App.ContentRoot = null;
});
await taskCompletionSource.Task;
}
}
}