Merge branch 'master' into test-chop-imageex
This commit is contained in:
Коммит
c2fc4ad5a7
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче