Merge branch 'master' into aleader/notifications-registry
This commit is contained in:
Коммит
389c6e1443
|
@ -125,7 +125,7 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator T(Box<T> box)
|
||||
{
|
||||
return Unsafe.Unbox<T>(box);
|
||||
return (T)(object)box;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -180,7 +180,6 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <summary>
|
||||
/// Throws an <see cref="InvalidCastException"/> when a cast from an invalid <see cref="object"/> is attempted.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidCastExceptionForGetFrom()
|
||||
{
|
||||
throw new InvalidCastException($"Can't cast the input object to the type Box<{typeof(T)}>");
|
||||
|
|
|
@ -33,6 +33,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// </summary>
|
||||
private const int DefaultInitialBufferSize = 256;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
|
@ -49,36 +54,52 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
public ArrayPoolBufferWriter()
|
||||
: this(ArrayPool<T>.Shared, DefaultInitialBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
public ArrayPoolBufferWriter(ArrayPool<T> pool)
|
||||
: this(pool, DefaultInitialBufferSize)
|
||||
{
|
||||
// Since we're using pooled arrays, we can rent the buffer with the
|
||||
// default size immediately, we don't need to use lazy initialization
|
||||
// to save unnecessary memory allocations in this case.
|
||||
this.array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize);
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
|
||||
public ArrayPoolBufferWriter(int initialCapacity)
|
||||
: this(ArrayPool<T>.Shared, initialCapacity)
|
||||
{
|
||||
if (initialCapacity <= 0)
|
||||
{
|
||||
ThrowArgumentOutOfRangeExceptionForInitialCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
this.array = ArrayPool<T>.Shared.Rent(initialCapacity);
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="initialCapacity"/> is not valid.</exception>
|
||||
public ArrayPoolBufferWriter(ArrayPool<T> pool, int initialCapacity)
|
||||
{
|
||||
// Since we're using pooled arrays, we can rent the buffer with the
|
||||
// default size immediately, we don't need to use lazy initialization
|
||||
// to save unnecessary memory allocations in this case.
|
||||
// Additionally, we don't need to manually throw the exception if
|
||||
// the requested size is not valid, as that'll be thrown automatically
|
||||
// by the array pool in use when we try to rent an array with that size.
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(initialCapacity);
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ArrayPoolBufferWriter{T}"/> class.
|
||||
/// </summary>
|
||||
~ArrayPoolBufferWriter() => this.Dispose();
|
||||
~ArrayPoolBufferWriter() => Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
Memory<T> IMemoryOwner<T>.Memory
|
||||
|
@ -182,6 +203,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
}
|
||||
|
||||
array.AsSpan(0, this.index).Clear();
|
||||
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
|
@ -250,7 +272,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
{
|
||||
int minimumSize = this.index + sizeHint;
|
||||
|
||||
ArrayPool<T>.Shared.Resize(ref this.array, minimumSize);
|
||||
this.pool.Resize(ref this.array, minimumSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +290,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
|
||||
this.array = null;
|
||||
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
this.pool.Return(array);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -286,19 +308,9 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
return $"Microsoft.Toolkit.HighPerformance.Buffers.ArrayPoolBufferWriter<{typeof(T)}>[{this.index}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the initial capacity is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInitialCapacity()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("initialCapacity", "The initial capacity must be a positive value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
|
||||
|
@ -307,7 +319,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
|
||||
|
@ -316,7 +327,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForAdvancedTooFar()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer has advanced too far");
|
||||
|
@ -325,7 +335,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException("The current buffer has already been disposed");
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <inheritdoc/>
|
||||
public Memory<T> GetMemory(int sizeHint = 0)
|
||||
{
|
||||
this.ValidateSizeHint(sizeHint);
|
||||
ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index);
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <inheritdoc/>
|
||||
public Span<T> GetSpan(int sizeHint = 0)
|
||||
{
|
||||
this.ValidateSizeHint(sizeHint);
|
||||
ValidateSizeHint(sizeHint);
|
||||
|
||||
return this.memory.Slice(this.index).Span;
|
||||
}
|
||||
|
@ -159,7 +159,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeCount()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count", "The count can't be a negative value");
|
||||
|
@ -168,7 +167,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the size hint is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForNegativeSizeHint()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("sizeHint", "The size hint can't be a negative value");
|
||||
|
@ -177,7 +175,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the requested count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForAdvancedTooFar()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer has advanced too far");
|
||||
|
@ -186,7 +183,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the requested size exceeds the capacity.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForCapacityExceeded()
|
||||
{
|
||||
throw new ArgumentException("The buffer writer doesn't have enough capacity left");
|
||||
|
|
|
@ -32,6 +32,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
private readonly int length;
|
||||
#pragma warning restore IDE0032
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
|
@ -41,12 +46,14 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
private MemoryOwner(int length, AllocationMode mode)
|
||||
private MemoryOwner(int length, ArrayPool<T> pool, AllocationMode mode)
|
||||
{
|
||||
this.start = 0;
|
||||
this.length = length;
|
||||
this.array = ArrayPool<T>.Shared.Rent(length);
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(length);
|
||||
|
||||
if (mode == AllocationMode.Clear)
|
||||
{
|
||||
|
@ -57,20 +64,22 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
|
||||
/// <param name="start">The starting offset within <paramref name="array"/>.</param>
|
||||
/// <param name="length">The length of the array to use.</param>
|
||||
private MemoryOwner(T[] array, int start, int length)
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
|
||||
/// <param name="array">The input <typeparamref name="T"/> array to use.</param>
|
||||
private MemoryOwner(int start, int length, ArrayPool<T> pool, T[] array)
|
||||
{
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
this.pool = pool;
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="MemoryOwner{T}"/> class.
|
||||
/// </summary>
|
||||
~MemoryOwner() => this.Dispose();
|
||||
~MemoryOwner() => Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty <see cref="MemoryOwner{T}"/> instance.
|
||||
|
@ -79,7 +88,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
public static MemoryOwner<T> Empty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new MemoryOwner<T>(0, AllocationMode.Default);
|
||||
get => new MemoryOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -91,7 +100,19 @@ 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, AllocationMode.Default);
|
||||
public static MemoryOwner<T> Allocate(int size) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="size">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
|
||||
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
||||
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool) => new MemoryOwner<T>(size, pool, AllocationMode.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
|
||||
|
@ -103,7 +124,20 @@ 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, mode);
|
||||
public static MemoryOwner<T> Allocate(int size, AllocationMode mode) => new MemoryOwner<T>(size, ArrayPool<T>.Shared, mode);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="size">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance currently in use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
||||
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MemoryOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new MemoryOwner<T>(size, pool, mode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the current instance
|
||||
|
@ -210,7 +244,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
ThrowInvalidLengthException();
|
||||
}
|
||||
|
||||
return new MemoryOwner<T>(array!, start, length);
|
||||
return new MemoryOwner<T>(start, length, this.pool, array!);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -227,7 +261,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
|
||||
this.array = null;
|
||||
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
this.pool.Return(array);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -251,7 +285,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="array"/> is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The current buffer has already been disposed");
|
||||
|
@ -260,7 +293,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="start"/> is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOffsetException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(start), "The input start parameter was not valid");
|
||||
|
@ -269,7 +301,6 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="length"/> is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidLengthException()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), "The input length parameter was not valid");
|
||||
|
|
|
@ -42,6 +42,11 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
private readonly int length;
|
||||
#pragma warning restore IDE0032
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ArrayPool{T}"/> instance used to rent <see cref="array"/>.
|
||||
/// </summary>
|
||||
private readonly ArrayPool<T> pool;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
|
@ -51,11 +56,13 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
private SpanOwner(int length, AllocationMode mode)
|
||||
private SpanOwner(int length, ArrayPool<T> pool, AllocationMode mode)
|
||||
{
|
||||
this.length = length;
|
||||
this.array = ArrayPool<T>.Shared.Rent(length);
|
||||
this.pool = pool;
|
||||
this.array = pool.Rent(length);
|
||||
|
||||
if (mode == AllocationMode.Clear)
|
||||
{
|
||||
|
@ -70,7 +77,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
public static SpanOwner<T> Empty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new SpanOwner<T>(0, AllocationMode.Default);
|
||||
get => new SpanOwner<T>(0, ArrayPool<T>.Shared, AllocationMode.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,7 +89,19 @@ 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, AllocationMode.Default);
|
||||
public static SpanOwner<T> Allocate(int size) => new SpanOwner<T>(size, ArrayPool<T>.Shared, AllocationMode.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="size">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
||||
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool) => new SpanOwner<T>(size, pool, AllocationMode.Default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
||||
|
@ -94,7 +113,20 @@ 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, mode);
|
||||
public static SpanOwner<T> Allocate(int size, AllocationMode mode) => new SpanOwner<T>(size, ArrayPool<T>.Shared, mode);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="size">The length of the new memory buffer to use.</param>
|
||||
/// <param name="pool">The <see cref="ArrayPool{T}"/> instance to use.</param>
|
||||
/// <param name="mode">Indicates the allocation mode to use for the new buffer to rent.</param>
|
||||
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="size"/> is not valid.</exception>
|
||||
/// <remarks>This method is just a proxy for the <see langword="private"/> constructor, for clarity.</remarks>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SpanOwner<T> Allocate(int size, ArrayPool<T> pool, AllocationMode mode) => new SpanOwner<T>(size, pool, mode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the current instance
|
||||
|
@ -111,7 +143,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
public Span<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new Span<T>(array, 0, this.length);
|
||||
get => new Span<T>(this.array, 0, this.length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -122,7 +154,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref T DangerousGetReference()
|
||||
{
|
||||
return ref array.DangerousGetReference();
|
||||
return ref this.array.DangerousGetReference();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -131,7 +163,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose()
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(array);
|
||||
this.pool.Return(this.array);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
@ -205,7 +205,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="column"/> is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidColumn()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(column), "The target column parameter was not valid");
|
||||
|
|
|
@ -160,7 +160,6 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the <see cref="row"/> is invalid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidRow()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row), "The target row parameter was not valid");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -42,8 +43,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ReadOnlySpanEnumerable{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpanEnumerable<T> GetEnumerator() => this;
|
||||
public readonly ReadOnlySpanEnumerable<T> GetEnumerator() => this;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
|
@ -67,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public Item Current
|
||||
public readonly Item Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
|
@ -54,8 +55,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ReadOnlySpanTokenizer{T}"/> instance targeting the current <see cref="ReadOnlySpan{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpanTokenizer<T> GetEnumerator() => this;
|
||||
public readonly ReadOnlySpanTokenizer<T> GetEnumerator() => this;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
|
@ -94,7 +96,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<T> Current
|
||||
public readonly ReadOnlySpan<T> Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.span.Slice(this.start, this.end - this.start);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -42,8 +43,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="SpanEnumerable{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanEnumerable<T> GetEnumerator() => this;
|
||||
public readonly SpanEnumerable<T> GetEnumerator() => this;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
|
@ -67,7 +69,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public Item Current
|
||||
public readonly Item Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
|
@ -76,12 +78,12 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
ref T r0 = ref MemoryMarshal.GetReference(this.span);
|
||||
ref T ri = ref Unsafe.Add(ref r0, this.index);
|
||||
|
||||
// On .NET Standard 2.1 we can save 4 bytes by piggybacking
|
||||
// the current index in the length of the wrapped span.
|
||||
// We're going to use the first item as the target reference,
|
||||
// and the length as a host for the current original offset.
|
||||
// This is not possible on .NET Standard 2.1 as we lack
|
||||
// the API to create spans from arbitrary references.
|
||||
// On .NET Standard 2.1 and .NET Core (or on any target that offers runtime
|
||||
// support for the Span<T> types), we can save 4 bytes by piggybacking the
|
||||
// current index in the length of the wrapped span. We're going to use the
|
||||
// first item as the target reference, and the length as a host for the
|
||||
// current original offset. This is not possible on eg. .NET Standard 2.0,
|
||||
// as we lack the API to create Span<T>-s from arbitrary references.
|
||||
return new Item(ref ri, this.index);
|
||||
#else
|
||||
return new Item(this.span, this.index);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
||||
|
@ -54,8 +55,9 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="SpanTokenizer{T}"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SpanTokenizer<T> GetEnumerator() => this;
|
||||
public readonly SpanTokenizer<T> GetEnumerator() => this;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
|
||||
|
@ -94,7 +96,7 @@ namespace Microsoft.Toolkit.HighPerformance.Enumerables
|
|||
/// <summary>
|
||||
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
|
||||
/// </summary>
|
||||
public Span<T> Current
|
||||
public readonly Span<T> Current
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.span.Slice(this.start, this.end - this.start);
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
T[] newArray = pool.Rent(newSize);
|
||||
int itemsToCopy = Math.Min(array.Length, newSize);
|
||||
|
||||
array.AsSpan(0, itemsToCopy).CopyTo(newArray);
|
||||
Array.Copy(array, 0, newArray, 0, itemsToCopy);
|
||||
|
||||
pool.Return(array, clearArray);
|
||||
|
||||
|
|
|
@ -102,7 +102,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target writer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForEndOfBuffer()
|
||||
{
|
||||
throw new ArgumentException("The current buffer writer can't contain the requested input data.");
|
||||
|
|
|
@ -164,7 +164,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <param name="value">The <typeparamref name="T"/> value to look for.</param>
|
||||
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Count<T>(this ReadOnlySpan<T> span, T value)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
|
|
|
@ -223,7 +223,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the given reference is out of range.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowArgumentOutOfRangeExceptionForInvalidReference()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value", "The input reference does not belong to an element of the input span");
|
||||
|
|
|
@ -245,7 +245,6 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> when <see cref="Read{T}"/> fails.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOperationExceptionForEndOfStream()
|
||||
{
|
||||
throw new InvalidOperationException("The stream didn't contain enough data to read the requested item");
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if !NETCOREAPP3_1
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Microsoft.Toolkit.HighPerformance.Enumerables;
|
||||
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
|
||||
|
||||
|
@ -107,7 +109,7 @@ namespace Microsoft.Toolkit.HighPerformance.Extensions
|
|||
/// {
|
||||
/// // Access the index and value of each item here...
|
||||
/// int index = item.Index;
|
||||
/// string value = item.Value;
|
||||
/// char value = item.Value;
|
||||
/// }
|
||||
/// </code>
|
||||
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Helpers
|
||||
{
|
||||
|
@ -15,7 +14,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid parameter is specified for the minimum actions per thread.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForInvalidMinimumActionsPerThread()
|
||||
{
|
||||
// Having the argument name here manually typed is
|
||||
|
@ -30,7 +28,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid start parameter is specified for 1D loops.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForStartGreaterThanEnd()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("start", "The start parameter must be less than or equal to end");
|
||||
|
@ -39,7 +36,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when a range has an index starting from an end.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForRangeIndexFromEnd(string name)
|
||||
{
|
||||
throw new ArgumentException("The bounds of the range can't start from an end", name);
|
||||
|
@ -48,7 +44,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid top parameter is specified for 2D loops.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForTopGreaterThanBottom()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("top", "The top parameter must be less than or equal to bottom");
|
||||
|
@ -57,7 +52,6 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when an invalid left parameter is specified for 2D loops.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForLeftGreaterThanRight()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("left", "The left parameter must be less than or equal to right");
|
||||
|
|
|
@ -129,7 +129,6 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
|
|
|
@ -113,7 +113,6 @@ namespace Microsoft.Toolkit.HighPerformance
|
|||
/// <summary>
|
||||
/// Throws a <see cref="InvalidOperationException"/> when trying to access <see cref="Value"/> for a default instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOperationException()
|
||||
{
|
||||
throw new InvalidOperationException("The current instance doesn't have a value that can be accessed");
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.HighPerformance.Streams
|
||||
{
|
||||
|
@ -16,7 +15,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when setting the <see cref="Stream.Position"/> property.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForPosition()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
|
||||
|
@ -25,7 +23,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentNullException"/> when an input buffer is <see langword="null"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentNullExceptionForBuffer()
|
||||
{
|
||||
throw new ArgumentNullException("buffer", "The buffer is null.");
|
||||
|
@ -34,7 +31,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForOffset()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("offset", "Offset can't be negative.");
|
||||
|
@ -43,7 +39,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the input count is negative.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentOutOfRangeExceptionForCount()
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count", "Count can't be negative.");
|
||||
|
@ -52,7 +47,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when the sum of offset and count exceeds the length of the target buffer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForLength()
|
||||
{
|
||||
throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer");
|
||||
|
@ -61,7 +55,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws a <see cref="NotSupportedException"/> when trying to write on a readonly stream.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowNotSupportedExceptionForCanWrite()
|
||||
{
|
||||
throw new NotSupportedException("The current stream doesn't support writing.");
|
||||
|
@ -70,7 +63,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
|
||||
{
|
||||
throw new ArgumentException("The current stream can't contain the requested input data.");
|
||||
|
@ -79,7 +71,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowNotSupportedExceptionForSetLength()
|
||||
{
|
||||
throw new NotSupportedException("Setting the length is not supported for this stream.");
|
||||
|
@ -89,7 +80,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
|
||||
/// </summary>
|
||||
/// <returns>Nothing, as this method throws unconditionally.</returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static long ThrowArgumentExceptionForSeekOrigin()
|
||||
{
|
||||
throw new ArgumentException("The input seek mode is not valid.", "origin");
|
||||
|
@ -98,7 +88,6 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
|
|||
/// <summary>
|
||||
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowObjectDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
|
||||
|
|
|
@ -25,11 +25,11 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
|
||||
/// This constructor will produce an instance that will use the <see cref="WeakReferenceMessenger.Default"/> instance
|
||||
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
|
||||
/// </remarks>
|
||||
protected ObservableRecipient()
|
||||
: this(Messaging.Messenger.Default)
|
||||
: this(WeakReferenceMessenger.Default)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
|
|||
/// <remarks>
|
||||
/// The base implementation registers all messages for this recipients that have been declared
|
||||
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
|
||||
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
|
||||
/// For more details on how this works, see the <see cref="IMessengerExtensions.RegisterAll"/> method.
|
||||
/// If you need more fine tuned control, want to register messages individually or just prefer
|
||||
/// the lambda-style syntax for message registration, override this method and register manually.
|
||||
/// </remarks>
|
||||
|
|
|
@ -7,8 +7,70 @@ using System.Diagnostics.Contracts;
|
|||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see langword="delegate"/> used to represent actions to invoke when a message is received.
|
||||
/// The recipient is given as an input argument to allow message registrations to avoid creating
|
||||
/// closures: if an instance method on a recipient needs to be invoked it is possible to just
|
||||
/// cast the recipient to the right type and then access the local method from that instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
|
||||
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
|
||||
/// <param name="recipient">The recipient that is receiving the message.</param>
|
||||
/// <param name="message">The message being received.</param>
|
||||
public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recipient, TMessage message)
|
||||
where TRecipient : class
|
||||
where TMessage : class;
|
||||
|
||||
/// <summary>
|
||||
/// An interface for a type providing the ability to exchange messages between different objects.
|
||||
/// This can be useful to decouple different modules of an application without having to keep strong
|
||||
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
|
||||
/// identified by a token, and to have different messengers in different sections of an applications.
|
||||
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
|
||||
/// <code>
|
||||
/// public sealed class LoginCompletedMessage { }
|
||||
/// </code>
|
||||
/// Then, register your a recipient for this message:
|
||||
/// <code>
|
||||
/// Messenger.Default.Register<MyRecipientType, LoginCompletedMessage>(this, (r, m) =>
|
||||
/// {
|
||||
/// // Handle the message here...
|
||||
/// });
|
||||
/// </code>
|
||||
/// The message handler here is a lambda expression taking two parameters: the recipient and the message.
|
||||
/// This is done to avoid the allocations for the closures that would've been generated if the expression
|
||||
/// had captured the current instance. The recipient type parameter is used so that the recipient can be
|
||||
/// directly accessed within the handler without the need to manually perform type casts. This allows the
|
||||
/// code to be less verbose and more reliable, as all the checks are done just at build time. If the handler
|
||||
/// is defined within the same type as the recipient, it is also possible to directly access private members.
|
||||
/// This allows the message handler to be a static method, which enables the C# compiler to perform a number
|
||||
/// of additional memory optimizations (such as caching the delegate, avoiding unnecessary memory allocations).
|
||||
/// Finally, send a message when needed, like so:
|
||||
/// <code>
|
||||
/// Messenger.Default.Send<LoginCompletedMessage>();
|
||||
/// </code>
|
||||
/// Additionally, the method group syntax can also be used to specify the message handler
|
||||
/// to invoke when receiving a message, if a method with the right signature is available
|
||||
/// in the current scope. This is helpful to keep the registration and handling logic separate.
|
||||
/// Following up from the previous example, consider a class having this method:
|
||||
/// <code>
|
||||
/// private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
|
||||
/// {
|
||||
/// // Handle the message there
|
||||
/// }
|
||||
/// </code>
|
||||
/// The registration can then be performed in a single line like so:
|
||||
/// <code>
|
||||
/// Messenger.Default.Register(this, Receive);
|
||||
/// </code>
|
||||
/// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient,TMessage}"/> instance
|
||||
/// compatible with <see cref="IMessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
|
||||
/// This will also work if multiple overloads of that method are available, each handling a different
|
||||
/// message type: the C# compiler will automatically pick the right one for the current message type.
|
||||
/// It is also possible to register message handlers explicitly using the <see cref="IRecipient{TMessage}"/> interface.
|
||||
/// To do so, the recipient just needs to implement the interface and then call the
|
||||
/// <see cref="IMessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
|
||||
/// all the handlers that are declared by the recipient type. Registration for individual handlers is supported as well.
|
||||
/// </summary>
|
||||
public interface IMessenger
|
||||
{
|
||||
|
@ -28,13 +90,15 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
/// <summary>
|
||||
/// Registers a recipient for a given type of message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
|
||||
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
|
||||
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
|
||||
/// <param name="recipient">The recipient that will receive the messages.</param>
|
||||
/// <param name="token">A token used to determine the receiving channel to use.</param>
|
||||
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
|
||||
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
|
||||
void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
|
||||
void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
|
||||
where TRecipient : class
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>;
|
||||
|
||||
|
@ -83,6 +147,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>;
|
||||
|
||||
/// <summary>
|
||||
/// Performs a cleanup on the current messenger.
|
||||
/// Invoking this method does not unregister any of the currently registered
|
||||
/// recipient, and it can be used to perform cleanup operations such as
|
||||
/// trimming the internal data structures of a messenger implementation.
|
||||
/// </summary>
|
||||
void Cleanup();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the <see cref="IMessenger"/> instance and unregisters all the existing recipients.
|
||||
/// </summary>
|
||||
|
|
|
@ -8,19 +8,20 @@ using System.Linq;
|
|||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="IMessenger"/> type.
|
||||
/// </summary>
|
||||
public static partial class MessengerExtensions
|
||||
public static class IMessengerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
|
||||
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
|
||||
/// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
|
||||
/// the <see cref="MessengerExtensions"/> type is referenced, even if that is done just to use methods
|
||||
/// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
|
||||
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
|
||||
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
|
||||
/// </summary>
|
||||
|
@ -32,7 +33,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
static MethodInfos()
|
||||
{
|
||||
RegisterIRecipient = (
|
||||
from methodInfo in typeof(MessengerExtensions).GetMethods()
|
||||
from methodInfo in typeof(IMessengerExtensions).GetMethods()
|
||||
where methodInfo.Name == nameof(Register) &&
|
||||
methodInfo.IsGenericMethod &&
|
||||
methodInfo.GetGenericArguments().Length == 2
|
||||
|
@ -174,7 +175,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
|
||||
where TMessage : class
|
||||
{
|
||||
messenger.Register<TMessage, Unit>(recipient, default, recipient.Receive);
|
||||
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -191,7 +192,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
messenger.Register<TMessage, TToken>(recipient, token, recipient.Receive);
|
||||
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -200,13 +201,47 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
|
||||
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
|
||||
/// <param name="recipient">The recipient that will receive the messages.</param>
|
||||
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
|
||||
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
|
||||
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
|
||||
public static void Register<TMessage>(this IMessenger messenger, object recipient, Action<TMessage> action)
|
||||
public static void Register<TMessage>(this IMessenger messenger, object recipient, MessageHandler<object, TMessage> handler)
|
||||
where TMessage : class
|
||||
{
|
||||
messenger.Register(recipient, default(Unit), action);
|
||||
messenger.Register(recipient, default(Unit), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a recipient for a given type of message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
|
||||
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
|
||||
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
|
||||
/// <param name="recipient">The recipient that will receive the messages.</param>
|
||||
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
|
||||
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
|
||||
public static void Register<TRecipient, TMessage>(this IMessenger messenger, TRecipient recipient, MessageHandler<TRecipient, TMessage> handler)
|
||||
where TRecipient : class
|
||||
where TMessage : class
|
||||
{
|
||||
messenger.Register(recipient, default(Unit), handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a recipient for a given type of message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
|
||||
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
|
||||
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
|
||||
/// <param name="recipient">The recipient that will receive the messages.</param>
|
||||
/// <param name="token">A token used to determine the receiving channel to use.</param>
|
||||
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
|
||||
public static void Register<TMessage, TToken>(this IMessenger messenger, object recipient, TToken token, MessageHandler<object, TMessage> handler)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
messenger.Register(recipient, token, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
|
@ -130,7 +130,11 @@ namespace Microsoft.Collections.Extensions
|
|||
this.entries = InitialEntries;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Dictionary{TKey,TValue}.ContainsKey"/>
|
||||
/// <summary>
|
||||
/// Checks whether or not the dictionary contains a pair with a specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to look for.</param>
|
||||
/// <returns>Whether or not the key was present in the dictionary.</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
Entry[] entries = this.entries;
|
||||
|
@ -176,7 +180,18 @@ namespace Microsoft.Collections.Extensions
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryRemove(TKey key, out object? result)
|
||||
public bool TryRemove(TKey key)
|
||||
{
|
||||
return TryRemove(key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a value with a specified key, if present.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to remove.</param>
|
||||
/// <param name="result">The removed value, if it was present.</param>
|
||||
/// <returns>Whether or not the key was present.</returns>
|
||||
public bool TryRemove(TKey key, out TValue? result)
|
||||
{
|
||||
Entry[] entries = this.entries;
|
||||
int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1);
|
||||
|
@ -218,13 +233,6 @@ namespace Microsoft.Collections.Extensions
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return TryRemove(key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value for the specified key, or, if the key is not present,
|
||||
/// adds an entry and returns the value by ref. This makes it possible to
|
|
@ -14,18 +14,10 @@ namespace Microsoft.Collections.Extensions
|
|||
where TKey : IEquatable<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to remove a value with a specified key.
|
||||
/// Tries to remove a value with a specified key, if present.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to remove.</param>
|
||||
/// <param name="result">The removed value, if it was present.</param>
|
||||
/// <returns>.Whether or not the key was present.</returns>
|
||||
bool TryRemove(TKey key, out object? result);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the dictionary with the specified key, if present.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the item to remove.</param>
|
||||
/// <returns>Whether or not an item was removed.</returns>
|
||||
bool Remove(TKey key);
|
||||
/// <returns>Whether or not the key was present.</returns>
|
||||
bool TryRemove(TKey key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple type representing an immutable pair of types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
|
||||
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
|
||||
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
|
||||
/// <see cref="TMessage"/> and <see cref="TToken"/> fields provide additional clarity reading
|
||||
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
|
||||
/// </remarks>
|
||||
internal readonly struct Type2 : IEquatable<Type2>
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of registered message.
|
||||
/// </summary>
|
||||
public readonly Type TMessage;
|
||||
|
||||
/// <summary>
|
||||
/// The type of registration token.
|
||||
/// </summary>
|
||||
public readonly Type TToken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Type2"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="tMessage">The type of registered message.</param>
|
||||
/// <param name="tToken">The type of registration token.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Type2(Type tMessage, Type tToken)
|
||||
{
|
||||
TMessage = tMessage;
|
||||
TToken = tToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Type2 other)
|
||||
{
|
||||
// We can't just use reference equality, as that's technically not guaranteed
|
||||
// to work and might fail in very rare cases (eg. with type forwarding between
|
||||
// different assemblies). Instead, we can use the == operator to compare for
|
||||
// equality, which still avoids the callvirt overhead of calling Type.Equals,
|
||||
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
|
||||
return
|
||||
TMessage == other.TMessage &&
|
||||
TToken == other.TToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Type2 other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
|
||||
// This is not a problem in this case since we already know that the base
|
||||
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
|
||||
int hash = RuntimeHelpers.GetHashCode(TMessage);
|
||||
|
||||
hash = (hash << 5) + hash;
|
||||
|
||||
hash += RuntimeHelpers.GetHashCode(TToken);
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty type representing a generic token with no specific value.
|
||||
/// </summary>
|
||||
internal readonly struct Unit : IEquatable<Unit>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Unit other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Unit;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="IMessenger"/> type.
|
||||
/// </summary>
|
||||
public static partial class MessengerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty type representing a generic token with no specific value.
|
||||
/// </summary>
|
||||
private readonly struct Unit : IEquatable<Unit>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Unit other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Unit;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,71 +8,46 @@ using System.Collections.Generic;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Microsoft.Collections.Extensions;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A type that can be used to exchange messages between different objects.
|
||||
/// This can be useful to decouple different modules of an application without having to keep strong
|
||||
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
|
||||
/// identified by a token, and to have different messengers in different sections of an applications.
|
||||
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
|
||||
/// <code>
|
||||
/// public sealed class LoginCompletedMessage { }
|
||||
/// </code>
|
||||
/// Then, register your a recipient for this message:
|
||||
/// <code>
|
||||
/// Messenger.Default.Register<LoginCompletedMessage>(this, m =>
|
||||
/// {
|
||||
/// // Handle the message here...
|
||||
/// });
|
||||
/// </code>
|
||||
/// Finally, send a message when needed, like so:
|
||||
/// <code>
|
||||
/// Messenger.Default.Send<LoginCompletedMessage>();
|
||||
/// </code>
|
||||
/// Additionally, the method group syntax can also be used to specify the action
|
||||
/// to invoke when receiving a message, if a method with the right signature is available
|
||||
/// in the current scope. This is helpful to keep the registration and handling logic separate.
|
||||
/// Following up from the previous example, consider a class having this method:
|
||||
/// <code>
|
||||
/// private void Receive(LoginCompletedMessage message)
|
||||
/// {
|
||||
/// // Handle the message there
|
||||
/// }
|
||||
/// </code>
|
||||
/// The registration can then be performed in a single line like so:
|
||||
/// <code>
|
||||
/// Messenger.Default.Register<LoginCompletedMessage>(this, Receive);
|
||||
/// </code>
|
||||
/// The C# compiler will automatically convert that expression to an <see cref="Action{T}"/> instance
|
||||
/// compatible with the <see cref="MessengerExtensions.Register{T}(IMessenger,object,Action{T})"/> method.
|
||||
/// This will also work if multiple overloads of that method are available, each handling a different
|
||||
/// message type: the C# compiler will automatically pick the right one for the current message type.
|
||||
/// For info on the other available features, check the <see cref="IMessenger"/> interface.
|
||||
/// A class providing a reference implementation for the <see cref="IMessenger"/> interface.
|
||||
/// </summary>
|
||||
public sealed class Messenger : IMessenger
|
||||
/// <remarks>
|
||||
/// This <see cref="IMessenger"/> implementation uses strong references to track the registered
|
||||
/// recipients, so it is necessary to manually unregister them when they're no longer needed.
|
||||
/// </remarks>
|
||||
public sealed class StrongReferenceMessenger : IMessenger
|
||||
{
|
||||
// The Messenger class uses the following logic to link stored instances together:
|
||||
// This messenger uses the following logic to link stored instances together:
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// DictionarySlim<Recipient, HashSet<IMapping>> recipientsMap;
|
||||
// | \________________[*]IDictionarySlim<Recipient, IDictionarySlim<TToken>>
|
||||
// | \___ / / /
|
||||
// | ________(recipients registrations)___________\________/ / __/
|
||||
// | / _______(channel registrations)_____\___________________/ /
|
||||
// | / / \ /
|
||||
// DictionarySlim<Recipient, DictionarySlim<TToken, Action<TMessage>>> mapping = Mapping<TMessage, TToken>
|
||||
// / / \ / /
|
||||
// ___(Type2.tToken)____/ / \______/___________________/
|
||||
// /________________(Type2.tMessage)____/ /
|
||||
// / ________________________________________/
|
||||
// | \____________/_________ /
|
||||
// | ________(recipients registrations)____________________/ \ /
|
||||
// | / ____(channel registrations)________________\____________/
|
||||
// | / / \
|
||||
// DictionarySlim<Recipient, DictionarySlim<TToken, MessageHandler<TRecipient, TMessage>>> mapping = Mapping<TMessage, TToken>
|
||||
// / / /
|
||||
// ___(Type2.TToken)____/ / /
|
||||
// /________________(Type2.TMessage)________________________/ /
|
||||
// / ____________________________________________________________/
|
||||
// / /
|
||||
// DictionarySlim<Type2, IMapping> typesMap;
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds
|
||||
// the references from registered recipients to handlers. The handlers are stored in a <TToken, Action<TMessage>>
|
||||
// dictionary, so that each recipient can have up to one registered handler for a given token, for each
|
||||
// message type. Each mapping is stored in the types map, which associates each pair of concrete types to its
|
||||
// Each combination of <TMessage, TToken> results in a concrete Mapping<TMessage, TToken> type, which holds the references
|
||||
// from registered recipients to handlers. The handlers are stored in a <TToken, MessageHandler<object, TMessage>> dictionary,
|
||||
// so that each recipient can have up to one registered handler for a given token, for each message type.
|
||||
// Note that the registered handlers are only stored as object references, even if they were actually of type
|
||||
// MessageHandler<TRecipient, TMessage>, to avoid unnecessary unsafe casts. Each handler is also generic with respect to the
|
||||
// recipient type, in order to allow the messenger to track and invoke type-specific handlers without using reflection and
|
||||
// without having to capture the input handler in a proxy delegate, causing one extra memory allocations and adding overhead.
|
||||
// This allows users to retain type information on each registered recipient, instead of having to manually cast each recipient
|
||||
// to the right type within the handler. The type conversion is guaranteed to be respected due to how the messenger type
|
||||
// itself works - as registered handlers are always invoked on their respective recipients.
|
||||
// Each mapping is stored in the types map, which associates each pair of concrete types to its
|
||||
// mapping instance. Mapping instances are exposed as IMapping items, as each will be a closed type over
|
||||
// a different combination of TMessage and TToken generic type parameters. Each existing recipient is also stored in
|
||||
// the main recipients map, along with a set of all the existing dictionaries of handlers for that recipient (for all
|
||||
|
@ -109,9 +84,9 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
private readonly DictionarySlim<Type2, IMapping> typesMap = new DictionarySlim<Type2, IMapping>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="Messenger"/> instance.
|
||||
/// Gets the default <see cref="StrongReferenceMessenger"/> instance.
|
||||
/// </summary>
|
||||
public static Messenger Default { get; } = new Messenger();
|
||||
public static StrongReferenceMessenger Default { get; } = new StrongReferenceMessenger();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
|
||||
|
@ -132,7 +107,8 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
|
||||
public void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
|
||||
where TRecipient : class
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
|
@ -141,22 +117,20 @@ 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);
|
||||
ref DictionarySlim<TToken, Action<TMessage>>? map = ref mapping.GetOrAddValueRef(key);
|
||||
ref DictionarySlim<TToken, object>? map = ref mapping.GetOrAddValueRef(key);
|
||||
|
||||
map ??= new DictionarySlim<TToken, Action<TMessage>>();
|
||||
map ??= new DictionarySlim<TToken, object>();
|
||||
|
||||
// Add the new registration entry
|
||||
ref Action<TMessage>? handler = ref map.GetOrAddValueRef(token);
|
||||
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
|
||||
|
||||
if (!(handler is null))
|
||||
if (!(registeredHandler is null))
|
||||
{
|
||||
ThrowInvalidOperationExceptionForDuplicateRegistration();
|
||||
}
|
||||
|
||||
handler = action;
|
||||
|
||||
// Update the total counter for handlers for the current type parameters
|
||||
mapping.TotalHandlersCount++;
|
||||
// Treat the input delegate as if it was covariant (see comments below in the Send method)
|
||||
registeredHandler = handler;
|
||||
|
||||
// Make sure this registration map is tracked for the current recipient
|
||||
ref HashSet<IMapping>? set = ref this.recipientsMap.GetOrAddValueRef(key);
|
||||
|
@ -183,37 +157,23 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
// Removes all the lists of registered handlers for the recipient
|
||||
foreach (IMapping mapping in set!)
|
||||
{
|
||||
if (mapping.TryRemove(key, out object? handlersMap))
|
||||
if (mapping.TryRemove(key) &&
|
||||
mapping.Count == 0)
|
||||
{
|
||||
// If this branch is taken, it means the target recipient to unregister
|
||||
// had at least one registered handler for the current <TToken, TMessage>
|
||||
// pair of type parameters, which here is masked out by the IMapping interface.
|
||||
// Before removing the handlers, we need to retrieve the count of how many handlers
|
||||
// are being removed, in order to update the total counter for the mapping.
|
||||
// Just casting the dictionary to the base interface and accessing the Count
|
||||
// property directly gives us O(1) access time to retrieve this count.
|
||||
// The handlers map is the IDictionary<TToken, TMessage> instance for the mapping.
|
||||
int handlersCount = Unsafe.As<IDictionarySlim>(handlersMap).Count;
|
||||
|
||||
mapping.TotalHandlersCount -= handlersCount;
|
||||
|
||||
if (mapping.Count == 0)
|
||||
{
|
||||
// Maps here are really of type Mapping<,> and with unknown type arguments.
|
||||
// If after removing the current recipient a given map becomes empty, it means
|
||||
// that there are no registered recipients at all for a given pair of message
|
||||
// and token types. In that case, we also remove the map from the types map.
|
||||
// The reason for keeping a key in each mapping is that removing items from a
|
||||
// dictionary (a hashed collection) only costs O(1) in the best case, while
|
||||
// if we had tried to iterate the whole dictionary every time we would have
|
||||
// paid an O(n) minimum cost for each single remove operation.
|
||||
this.typesMap.Remove(mapping.TypeArguments);
|
||||
}
|
||||
// Maps here are really of type Mapping<,> and with unknown type arguments.
|
||||
// If after removing the current recipient a given map becomes empty, it means
|
||||
// that there are no registered recipients at all for a given pair of message
|
||||
// and token types. In that case, we also remove the map from the types map.
|
||||
// The reason for keeping a key in each mapping is that removing items from a
|
||||
// dictionary (a hashed collection) only costs O(1) in the best case, while
|
||||
// if we had tried to iterate the whole dictionary every time we would have
|
||||
// paid an O(n) minimum cost for each single remove operation.
|
||||
this.typesMap.TryRemove(mapping.TypeArguments, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the associated set in the recipients map
|
||||
this.recipientsMap.Remove(key);
|
||||
this.recipientsMap.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,7 +182,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
bool lockTaken = false;
|
||||
IDictionarySlim<Recipient, IDictionarySlim<TToken>>[]? maps = null;
|
||||
object[]? maps = null;
|
||||
int i = 0;
|
||||
|
||||
// We use an explicit try/finally block here instead of the lock syntax so that we can use a single
|
||||
|
@ -243,11 +203,16 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
return;
|
||||
}
|
||||
|
||||
// Copy the candidate mappings for the target recipient to a local
|
||||
// array, as we can't modify the contents of the set while iterating it.
|
||||
// The rented buffer is oversized and will also include mappings for
|
||||
// handlers of messages that are registered through a different token.
|
||||
maps = ArrayPool<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>.Shared.Rent(set!.Count);
|
||||
// Copy the candidate mappings for the target recipient to a local array, as we can't modify the
|
||||
// contents of the set while iterating it. The rented buffer is oversized and will also include
|
||||
// mappings for handlers of messages that are registered through a different token. Note that
|
||||
// we're using just an object array to minimize the number of total rented buffers, that would
|
||||
// just remain in the shared pool unused, other than when they are rented here. Instead, we're
|
||||
// using a type that would possibly also be used by the users of the library, which increases
|
||||
// the opportunities to reuse existing buffers for both. When we need to reference an item
|
||||
// stored in the buffer with the type we know it will have, we use Unsafe.As<T> to avoid the
|
||||
// expensive type check in the cast, since we already know the assignment will be valid.
|
||||
maps = ArrayPool<object>.Shared.Rent(set!.Count);
|
||||
|
||||
foreach (IMapping item in set)
|
||||
{
|
||||
|
@ -265,8 +230,10 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
// without having to know the concrete type in advance, and without having
|
||||
// to deal with reflection: we can just check if the type of the closed interface
|
||||
// matches with the token type currently in use, and operate on those instances.
|
||||
foreach (IDictionarySlim<Recipient, IDictionarySlim<TToken>> map in maps.AsSpan(0, i))
|
||||
foreach (object obj in maps.AsSpan(0, i))
|
||||
{
|
||||
var map = Unsafe.As<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>(obj);
|
||||
|
||||
// We don't need whether or not the map contains the recipient, as the
|
||||
// sequence of maps has already been copied from the set containing all
|
||||
// the mappings for the target recipients: it is guaranteed to be here.
|
||||
|
@ -274,36 +241,22 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
|
||||
// Try to remove the registered handler for the input token,
|
||||
// for the current message type (unknown from here).
|
||||
if (holder.Remove(token))
|
||||
if (holder.TryRemove(token) &&
|
||||
holder.Count == 0)
|
||||
{
|
||||
// As above, we need to update the total number of registered handlers for the map.
|
||||
// In this case we also know that the current TToken type parameter is of interest
|
||||
// for the current method, as we're only unsubscribing handlers using that token.
|
||||
// This is because we're already working on the final <TToken, TMessage> mapping,
|
||||
// which associates a single handler with a given token, for a given recipient.
|
||||
// This means that we don't have to retrieve the count to subtract in this case,
|
||||
// we're just removing a single handler at a time. So, we just decrement the total.
|
||||
Unsafe.As<IMapping>(map).TotalHandlersCount--;
|
||||
// If the map is empty, remove the recipient entirely from its container
|
||||
map.TryRemove(key);
|
||||
|
||||
if (holder.Count == 0)
|
||||
// If no handlers are left at all for the recipient, across all
|
||||
// message types and token types, remove the set of mappings
|
||||
// entirely for the current recipient, and lost the strong
|
||||
// reference to it as well. This is the same situation that
|
||||
// would've been achieved by just calling UnregisterAll(recipient).
|
||||
if (map.Count == 0 &&
|
||||
set.Remove(Unsafe.As<IMapping>(map)) &&
|
||||
set.Count == 0)
|
||||
{
|
||||
// If the map is empty, remove the recipient entirely from its container
|
||||
map.Remove(key);
|
||||
|
||||
if (map.Count == 0)
|
||||
{
|
||||
// If no handlers are left at all for the recipient, across all
|
||||
// message types and token types, remove the set of mappings
|
||||
// entirely for the current recipient, and lost the strong
|
||||
// reference to it as well. This is the same situation that
|
||||
// would've been achieved by just calling UnregisterAll(recipient).
|
||||
set.Remove(Unsafe.As<IMapping>(map));
|
||||
|
||||
if (set.Count == 0)
|
||||
{
|
||||
this.recipientsMap.Remove(key);
|
||||
}
|
||||
}
|
||||
this.recipientsMap.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -324,7 +277,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
{
|
||||
maps.AsSpan(0, i).Clear();
|
||||
|
||||
ArrayPool<IDictionarySlim<Recipient, IDictionarySlim<TToken>>>.Shared.Return(maps);
|
||||
ArrayPool<object>.Shared.Return(maps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,84 +297,92 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
|
||||
var key = new Recipient(recipient);
|
||||
|
||||
if (!mapping!.TryGetValue(key, out DictionarySlim<TToken, Action<TMessage>>? dictionary))
|
||||
if (!mapping!.TryGetValue(key, out DictionarySlim<TToken, object>? dictionary))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the target handler
|
||||
if (dictionary!.Remove(token))
|
||||
if (dictionary!.TryRemove(token, out _) &&
|
||||
dictionary.Count == 0)
|
||||
{
|
||||
// Decrement the total count, as above
|
||||
mapping.TotalHandlersCount--;
|
||||
|
||||
// If the map is empty, it means that the current recipient has no remaining
|
||||
// registered handlers for the current <TMessage, TToken> combination, regardless,
|
||||
// of the specific token value (ie. the channel used to receive messages of that type).
|
||||
// We can remove the map entirely from this container, and remove the link to the map itself
|
||||
// to the current mapping between existing registered recipients (or entire recipients too).
|
||||
if (dictionary.Count == 0)
|
||||
mapping.TryRemove(key, out _);
|
||||
|
||||
HashSet<IMapping> set = this.recipientsMap[key];
|
||||
|
||||
if (set.Remove(mapping) &&
|
||||
set.Count == 0)
|
||||
{
|
||||
mapping.Remove(key);
|
||||
|
||||
HashSet<IMapping> set = this.recipientsMap[key];
|
||||
|
||||
set.Remove(mapping);
|
||||
|
||||
if (set.Count == 0)
|
||||
{
|
||||
this.recipientsMap.Remove(key);
|
||||
}
|
||||
this.recipientsMap.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
|
||||
public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
Action<TMessage>[] entries;
|
||||
object[] handlers;
|
||||
object[] recipients;
|
||||
ref object handlersRef = ref Unsafe.AsRef<object>(null);
|
||||
ref object recipientsRef = ref Unsafe.AsRef<object>(null);
|
||||
int i = 0;
|
||||
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
// Check whether there are any registered recipients
|
||||
if (!TryGetMapping(out Mapping<TMessage, TToken>? mapping))
|
||||
_ = TryGetMapping(out Mapping<TMessage, TToken>? mapping);
|
||||
|
||||
// We need to make a local copy of the currently registered handlers, since users might
|
||||
// try to unregister (or register) new handlers from inside one of the currently existing
|
||||
// handlers. We can use memory pooling to reuse arrays, to minimize the average memory
|
||||
// usage. In practice, we usually just need to pay the small overhead of copying the items.
|
||||
// The current mapping contains all the currently registered recipients and handlers for
|
||||
// the <TMessage, TToken> combination in use. In the worst case scenario, all recipients
|
||||
// will have a registered handler with a token matching the input one, meaning that we could
|
||||
// have at worst a number of pending handlers to invoke equal to the total number of recipient
|
||||
// in the mapping. This relies on the fact that tokens are unique, and that there is only
|
||||
// one handler associated with a given token. We can use this upper bound as the requested
|
||||
// size for each array rented from the pool, which guarantees that we'll have enough space.
|
||||
int totalHandlersCount = mapping?.Count ?? 0;
|
||||
|
||||
if (totalHandlersCount == 0)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
// We need to make a local copy of the currently registered handlers,
|
||||
// since users might try to unregister (or register) new handlers from
|
||||
// inside one of the currently existing handlers. We can use memory pooling
|
||||
// to reuse arrays, to minimize the average memory usage. In practice,
|
||||
// we usually just need to pay the small overhead of copying the items.
|
||||
entries = ArrayPool<Action<TMessage>>.Shared.Rent(mapping!.TotalHandlersCount);
|
||||
handlers = ArrayPool<object>.Shared.Rent(totalHandlersCount);
|
||||
recipients = ArrayPool<object>.Shared.Rent(totalHandlersCount);
|
||||
handlersRef = ref handlers[0];
|
||||
recipientsRef = ref recipients[0];
|
||||
|
||||
// Copy the handlers to the local collection.
|
||||
// Both types being enumerate expose a struct enumerator,
|
||||
// so we're not actually allocating the enumerator here.
|
||||
// The array is oversized at this point, since it also includes
|
||||
// handlers for different tokens. We can reuse the same variable
|
||||
// to count the number of matching handlers to invoke later on.
|
||||
// This will be the array slice with valid actions in the rented buffer.
|
||||
var mappingEnumerator = mapping.GetEnumerator();
|
||||
// This will be the array slice with valid handler in the rented buffer.
|
||||
var mappingEnumerator = mapping!.GetEnumerator();
|
||||
|
||||
// Explicit enumerator usage here as we're using a custom one
|
||||
// that doesn't expose the single standard Current property.
|
||||
while (mappingEnumerator.MoveNext())
|
||||
{
|
||||
var pairsEnumerator = mappingEnumerator.Value.GetEnumerator();
|
||||
object recipient = mappingEnumerator.Key.Target;
|
||||
|
||||
while (pairsEnumerator.MoveNext())
|
||||
// Pick the target handler, if the token is a match for the recipient
|
||||
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
|
||||
{
|
||||
// Only select the ones with a matching token
|
||||
if (pairsEnumerator.Key.Equals(token))
|
||||
{
|
||||
entries[i++] = pairsEnumerator.Value;
|
||||
}
|
||||
// We can manually offset here to skip the bounds checks in this inner loop when
|
||||
// indexing the array (the size is already verified and guaranteed to be enough).
|
||||
Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)i) = handler!;
|
||||
Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)i++) = recipient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,23 +390,44 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
try
|
||||
{
|
||||
// Invoke all the necessary handlers on the local copy of entries
|
||||
foreach (var entry in entries.AsSpan(0, i))
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
entry(message);
|
||||
// We're doing an unsafe cast to skip the type checks again.
|
||||
// See the comments in the UnregisterAll method for more info.
|
||||
object handler = Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)j);
|
||||
object recipient = Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)j);
|
||||
|
||||
// Here we perform an unsafe cast to enable covariance for delegate types.
|
||||
// We know that the input recipient will always respect the type constraints
|
||||
// of each original input delegate, and doing so allows us to still invoke
|
||||
// them all from here without worrying about specific generic type arguments.
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(handler)(recipient, message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// As before, we also need to clear it first to avoid having potentially long
|
||||
// lasting memory leaks due to leftover references being stored in the pool.
|
||||
entries.AsSpan(0, i).Clear();
|
||||
handlers.AsSpan(0, i).Clear();
|
||||
recipients.AsSpan(0, i).Clear();
|
||||
|
||||
ArrayPool<Action<TMessage>>.Shared.Return(entries);
|
||||
ArrayPool<object>.Shared.Return(handlers);
|
||||
ArrayPool<object>.Shared.Return(recipients);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IMessenger.Cleanup()
|
||||
{
|
||||
// The current implementation doesn't require any kind of cleanup operation, as
|
||||
// all the internal data structures are already kept in sync whenever a recipient
|
||||
// is added or removed. This method is implemented through an explicit interface
|
||||
// implementation so that developers using this type directly will not see it in
|
||||
// the API surface (as it wouldn't be useful anyway, since it's a no-op here).
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reset()
|
||||
{
|
||||
|
@ -515,7 +497,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
/// This type is defined for simplicity and as a workaround for the lack of support for using type aliases
|
||||
/// over open generic types in C# (using type aliases can only be used for concrete, closed types).
|
||||
/// </remarks>
|
||||
private sealed class Mapping<TMessage, TToken> : DictionarySlim<Recipient, DictionarySlim<TToken, Action<TMessage>>>, IMapping
|
||||
private sealed class Mapping<TMessage, TToken> : DictionarySlim<Recipient, DictionarySlim<TToken, object>>, IMapping
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
|
@ -529,9 +511,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
|
||||
/// <inheritdoc/>
|
||||
public Type2 TypeArguments { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int TotalHandlersCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -544,11 +523,6 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
/// Gets the <see cref="Type2"/> instance representing the current type arguments.
|
||||
/// </summary>
|
||||
Type2 TypeArguments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total number of handlers in the current instance.
|
||||
/// </summary>
|
||||
int TotalHandlersCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -567,7 +541,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
/// <summary>
|
||||
/// The registered recipient.
|
||||
/// </summary>
|
||||
private readonly object target;
|
||||
public readonly object Target;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Recipient"/> struct.
|
||||
|
@ -576,14 +550,14 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Recipient(object target)
|
||||
{
|
||||
this.target = target;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Recipient other)
|
||||
{
|
||||
return ReferenceEquals(this.target, other.target);
|
||||
return ReferenceEquals(Target, other.Target);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -596,81 +570,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(this.target);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple type representing an immutable pair of types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type replaces a simple <see cref="ValueTuple{T1,T2}"/> as it's faster in its
|
||||
/// <see cref="GetHashCode"/> and <see cref="IEquatable{T}.Equals(T)"/> methods, and because
|
||||
/// unlike a value tuple it exposes its fields as immutable. Additionally, the
|
||||
/// <see cref="tMessage"/> and <see cref="tToken"/> fields provide additional clarity reading
|
||||
/// the code compared to <see cref="ValueTuple{T1,T2}.Item1"/> and <see cref="ValueTuple{T1,T2}.Item2"/>.
|
||||
/// </remarks>
|
||||
private readonly struct Type2 : IEquatable<Type2>
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of registered message.
|
||||
/// </summary>
|
||||
private readonly Type tMessage;
|
||||
|
||||
/// <summary>
|
||||
/// The type of registration token.
|
||||
/// </summary>
|
||||
private readonly Type tToken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Type2"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="tMessage">The type of registered message.</param>
|
||||
/// <param name="tToken">The type of registration token.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Type2(Type tMessage, Type tToken)
|
||||
{
|
||||
this.tMessage = tMessage;
|
||||
this.tToken = tToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(Type2 other)
|
||||
{
|
||||
// We can't just use reference equality, as that's technically not guaranteed
|
||||
// to work and might fail in very rare cases (eg. with type forwarding between
|
||||
// different assemblies). Instead, we can use the == operator to compare for
|
||||
// equality, which still avoids the callvirt overhead of calling Type.Equals,
|
||||
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
|
||||
return
|
||||
this.tMessage == other.tMessage &&
|
||||
this.tToken == other.tToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Type2 other && Equals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// To combine the two hashes, we can simply use the fast djb2 hash algorithm.
|
||||
// This is not a problem in this case since we already know that the base
|
||||
// RuntimeHelpers.GetHashCode method is providing hashes with a good enough distribution.
|
||||
int hash = RuntimeHelpers.GetHashCode(this.tMessage);
|
||||
|
||||
hash = (hash << 5) + hash;
|
||||
|
||||
hash += RuntimeHelpers.GetHashCode(this.tToken);
|
||||
|
||||
return hash;
|
||||
}
|
||||
return RuntimeHelpers.GetHashCode(this.Target);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,488 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Collections.Extensions;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
|
||||
#if NETSTANDARD2_1
|
||||
using RecipientsTable = System.Runtime.CompilerServices.ConditionalWeakTable<object, Microsoft.Collections.Extensions.IDictionarySlim>;
|
||||
#else
|
||||
using RecipientsTable = Microsoft.Toolkit.Mvvm.Messaging.WeakReferenceMessenger.ConditionalWeakTable<object, Microsoft.Collections.Extensions.IDictionarySlim>;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A class providing a reference implementation for the <see cref="IMessenger"/> interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This <see cref="IMessenger"/> implementation uses weak references to track the registered
|
||||
/// recipients, so it is not necessary to manually unregister them when they're no longer needed.
|
||||
/// </remarks>
|
||||
public sealed class WeakReferenceMessenger : IMessenger
|
||||
{
|
||||
// This messenger uses the following logic to link stored instances together:
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// DictionarySlim<TToken, MessageHandler<TRecipient, TMessage>> mapping
|
||||
// / / /
|
||||
// ___(Type2.TToken)___/ / /
|
||||
// /_________________(Type2.TMessage)______________________/ /
|
||||
// / ___________________________/
|
||||
// / /
|
||||
// DictionarySlim<Type2, ConditionalWeakTable<object, IDictionarySlim>> recipientsMap;
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// Just like in the strong reference variant, each pair of message and token types is used as a key in the
|
||||
// recipients map. In this case, the values in the dictionary are ConditionalWeakTable<,> instances, that
|
||||
// link each registered recipient to a map of currently registered handlers, through a weak reference.
|
||||
// The value in each conditional table is Dictionary<TToken, MessageHandler<TRecipient, TMessage>>, using
|
||||
// the same unsafe cast as before to allow the generic handler delegates to be invoked without knowing
|
||||
// what type each recipient was registered with, and without the need to use reflection.
|
||||
|
||||
/// <summary>
|
||||
/// The map of currently registered recipients for all message types.
|
||||
/// </summary>
|
||||
private readonly DictionarySlim<Type2, RecipientsTable> recipientsMap = new DictionarySlim<Type2, RecipientsTable>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="WeakReferenceMessenger"/> instance.
|
||||
/// </summary>
|
||||
public static WeakReferenceMessenger Default { get; } = new WeakReferenceMessenger();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
Type2 type2 = new Type2(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
|
||||
where TRecipient : class
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
Type2 type2 = new Type2(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);
|
||||
|
||||
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>()));
|
||||
|
||||
// Add the new registration entry
|
||||
ref object? registeredHandler = ref map.GetOrAddValueRef(token);
|
||||
|
||||
if (!(registeredHandler is null))
|
||||
{
|
||||
ThrowInvalidOperationExceptionForDuplicateRegistration();
|
||||
}
|
||||
|
||||
// Store the input handler
|
||||
registeredHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterAll(object recipient)
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
var enumerator = this.recipientsMap.GetEnumerator();
|
||||
|
||||
// Traverse all the existing conditional tables and remove all the ones
|
||||
// with the target recipient as key. We don't perform a cleanup here,
|
||||
// as that is responsability of a separate method defined below.
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
enumerator.Value.Remove(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterAll<TToken>(object recipient, TToken token)
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
var enumerator = this.recipientsMap.GetEnumerator();
|
||||
|
||||
// Same as above, with the difference being that this time we only go through
|
||||
// the conditional tables having a matching token type as key, and that we
|
||||
// only try to remove handlers with a matching token, if any.
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (enumerator.Key.TToken == typeof(TToken) &&
|
||||
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
|
||||
{
|
||||
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unregister<TMessage, TToken>(object recipient, TToken token)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
var type2 = new Type2(typeof(TMessage), typeof(TToken));
|
||||
var enumerator = this.recipientsMap.GetEnumerator();
|
||||
|
||||
// Traverse all the existing token and message pairs matching the current type
|
||||
// arguments, and remove all the handlers with a matching token, as above.
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (enumerator.Key.Equals(type2) &&
|
||||
enumerator.Value.TryGetValue(recipient, out IDictionarySlim mapping))
|
||||
{
|
||||
Unsafe.As<DictionarySlim<TToken, object>>(mapping).TryRemove(token, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
|
||||
where TMessage : class
|
||||
where TToken : IEquatable<TToken>
|
||||
{
|
||||
ArrayPoolBufferWriter<object> recipients;
|
||||
ArrayPoolBufferWriter<object> handlers;
|
||||
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
Type2 type2 = new Type2(typeof(TMessage), typeof(TToken));
|
||||
|
||||
// Try to get the target table
|
||||
if (!this.recipientsMap.TryGetValue(type2, out RecipientsTable? table))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
recipients = ArrayPoolBufferWriter<object>.Create();
|
||||
handlers = ArrayPoolBufferWriter<object>.Create();
|
||||
|
||||
// We need a local, temporary copy of all the pending recipients and handlers to
|
||||
// invoke, to avoid issues with handlers unregistering from messages while we're
|
||||
// holding the lock. To do this, we can just traverse the conditional table in use
|
||||
// to enumerate all the existing recipients for the token and message types pair
|
||||
// corresponding to the generic arguments for this invocation, and then track the
|
||||
// handlers with a matching token, and their corresponding recipients.
|
||||
foreach (KeyValuePair<object, IDictionarySlim> pair in table!)
|
||||
{
|
||||
var map = Unsafe.As<DictionarySlim<TToken, object>>(pair.Value);
|
||||
|
||||
if (map.TryGetValue(token, out object? handler))
|
||||
{
|
||||
recipients.Add(pair.Key);
|
||||
handlers.Add(handler!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadOnlySpan<object>
|
||||
recipientsSpan = recipients.Span,
|
||||
handlersSpan = handlers.Span;
|
||||
|
||||
for (int i = 0; i < recipientsSpan.Length; i++)
|
||||
{
|
||||
// Just like in the other messenger, here we need an unsafe cast to be able to
|
||||
// invoke a generic delegate with a contravariant input argument, with a less
|
||||
// derived reference, without reflection. This is guaranteed to work by how the
|
||||
// messenger tracks registered recipients and their associated handlers, so the
|
||||
// type conversion will always be valid (the recipients are the rigth instances).
|
||||
Unsafe.As<MessageHandler<object, TMessage>>(handlersSpan[i])(recipientsSpan[i], message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
recipients.Dispose();
|
||||
handlers.Dispose();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Cleanup()
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
using ArrayPoolBufferWriter<Type2> type2s = ArrayPoolBufferWriter<Type2>.Create();
|
||||
using ArrayPoolBufferWriter<object> emptyRecipients = ArrayPoolBufferWriter<object>.Create();
|
||||
|
||||
var enumerator = this.recipientsMap.GetEnumerator();
|
||||
|
||||
// First, we go through all the currently registered pairs of token and message types.
|
||||
// These represents all the combinations of generic arguments with at least one registered
|
||||
// handler, with the exception of those with recipients that have already been collected.
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
emptyRecipients.Reset();
|
||||
|
||||
bool hasAtLeastOneHandler = false;
|
||||
|
||||
// Go through the currently alive recipients to look for those with no handlers left. We track
|
||||
// the ones we find to remove them outside of the loop (can't modify during enumeration).
|
||||
foreach (KeyValuePair<object, IDictionarySlim> pair in enumerator.Value)
|
||||
{
|
||||
if (pair.Value.Count == 0)
|
||||
{
|
||||
emptyRecipients.Add(pair.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasAtLeastOneHandler = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the handler maps for recipients that are still alive but with no handlers
|
||||
foreach (object recipient in emptyRecipients.Span)
|
||||
{
|
||||
enumerator.Value.Remove(recipient);
|
||||
}
|
||||
|
||||
// Track the type combinations with no recipients or handlers left
|
||||
if (!hasAtLeastOneHandler)
|
||||
{
|
||||
type2s.Add(enumerator.Key);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all the mappings with no handlers left
|
||||
foreach (Type2 key in type2s.Span)
|
||||
{
|
||||
this.recipientsMap.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reset()
|
||||
{
|
||||
lock (this.recipientsMap)
|
||||
{
|
||||
this.recipientsMap.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD2_1
|
||||
/// <summary>
|
||||
/// A wrapper for <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/>
|
||||
/// that backports the enumerable support to .NET Standard 2.0 through an auxiliary list.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Tke key of items to store in the table.</typeparam>
|
||||
/// <typeparam name="TValue">The values to store in the table.</typeparam>
|
||||
internal sealed class ConditionalWeakTable<TKey, TValue>
|
||||
where TKey : class
|
||||
where TValue : class?
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}"/> instance.
|
||||
/// </summary>
|
||||
private readonly System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue> table;
|
||||
|
||||
/// <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>>();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.TryGetValue"/>
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return this.table.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.GetValue"/>
|
||||
public TValue GetValue(TKey key, System.Runtime.CompilerServices.ConditionalWeakTable<TKey, TValue>.CreateValueCallback createValueCallback)
|
||||
{
|
||||
// Get or create the value. When this method returns, the key will be present in the table
|
||||
TValue value = this.table.GetValue(key, createValueCallback);
|
||||
|
||||
// Check if the list of keys contains the given key.
|
||||
// If it does, we can just stop here and return the result.
|
||||
foreach (WeakReference<TKey> node in this.keys)
|
||||
{
|
||||
if (node.TryGetTarget(out TKey? target) &&
|
||||
ReferenceEquals(target, key))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the key to the list of weak references to track it
|
||||
this.keys.AddFirst(new WeakReference<TKey>(key));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey,TValue}.Remove"/>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return this.table.Remove(key);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
// Get the key and value for the current node
|
||||
if (node.Value.TryGetTarget(out TKey? target) &&
|
||||
this.table.TryGetValue(target!, out TValue value))
|
||||
{
|
||||
yield return new KeyValuePair<TKey, TValue>(target, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the current key has been collected, trim the list
|
||||
this.keys.Remove(node);
|
||||
}
|
||||
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// A simple buffer writer implementation using pooled arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to store in the list.</typeparam>
|
||||
/// <remarks>
|
||||
/// This type is a <see langword="ref"/> <see langword="struct"/> to avoid the object allocation and to
|
||||
/// enable the pattern-based <see cref="IDisposable"/> support. We aren't worried with consumers not
|
||||
/// using this type correctly since it's private and only accessible within the parent type.
|
||||
/// </remarks>
|
||||
private ref struct ArrayPoolBufferWriter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default buffer size to use to expand empty arrays.
|
||||
/// </summary>
|
||||
private const int DefaultInitialBufferSize = 128;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
private T[] array;
|
||||
|
||||
/// <summary>
|
||||
/// The starting offset within <see cref="array"/>.
|
||||
/// </summary>
|
||||
private int index;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArrayPoolBufferWriter{T}"/> struct.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ArrayPoolBufferWriter<T> Create()
|
||||
{
|
||||
return new ArrayPoolBufferWriter<T> { array = ArrayPool<T>.Shared.Rent(DefaultInitialBufferSize) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ReadOnlySpan{T}"/> with the current items.
|
||||
/// </summary>
|
||||
public ReadOnlySpan<T> Span
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this.array.AsSpan(0, this.index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the current collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
if (this.index == this.array.Length)
|
||||
{
|
||||
ResizeBuffer();
|
||||
}
|
||||
|
||||
this.array[this.index++] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the underlying array and the stored items.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
Array.Clear(this.array, 0, this.index);
|
||||
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes <see cref="array"/> when there is no space left for new items.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ResizeBuffer()
|
||||
{
|
||||
T[] rent = ArrayPool<T>.Shared.Rent(this.index << 2);
|
||||
|
||||
Array.Copy(this.array, 0, rent, 0, this.index);
|
||||
Array.Clear(this.array, 0, this.index);
|
||||
|
||||
ArrayPool<T>.Shared.Return(this.array);
|
||||
|
||||
this.array = rent;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IDisposable.Dispose"/>
|
||||
public void Dispose()
|
||||
{
|
||||
Array.Clear(this.array, 0, this.index);
|
||||
|
||||
ArrayPool<T>.Shared.Return(this.array);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> when trying to add a duplicate handler.
|
||||
/// </summary>
|
||||
private static void ThrowInvalidOperationExceptionForDuplicateRegistration()
|
||||
{
|
||||
throw new InvalidOperationException("The target recipient has already subscribed to the target message");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Title>Windows Community Toolkit MVVM Toolkit</Title>
|
||||
<Description>
|
||||
This package includes a .NET Standard MVVM library with helpers such as:
|
||||
|
|
|
@ -50,13 +50,12 @@
|
|||
Margin="5,0,0,0"
|
||||
Width="50">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon
|
||||
Data="{StaticResource GithubIcon}"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Margin="-10">
|
||||
<PathIcon Margin="-3"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Data="{StaticResource GithubIcon}">
|
||||
<PathIcon.RenderTransform>
|
||||
<CompositeTransform TranslateY="8" TranslateX="3" />
|
||||
<CompositeTransform TranslateY="5" TranslateX="-5" />
|
||||
</PathIcon.RenderTransform>
|
||||
</PathIcon>
|
||||
</AppBarButton.Icon>
|
||||
|
|
|
@ -155,10 +155,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Render
|
|||
var columnIndex = Grid.GetColumn(child);
|
||||
var rowIndex = Grid.GetRow(child);
|
||||
|
||||
var rect = new Rect(0, 0, 0, 0)
|
||||
{
|
||||
X = _borderThickness
|
||||
};
|
||||
var rect = new Rect(_borderThickness, 0, 0, 0);
|
||||
|
||||
for (int col = 0; col < columnIndex; col++)
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Microsoft.Toolkit.Uwp.UI.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
@ -283,7 +284,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
double centerLeft = 0;
|
||||
double centerTop = 0;
|
||||
|
||||
Clip = new RectangleGeometry { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) };
|
||||
Clip = new RectangleGeometry { Rect = finalSize.ToRect() };
|
||||
|
||||
for (int i = 0; i < Children.Count; i++)
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.DirectX;
|
||||
using Windows.Graphics.Display;
|
||||
|
@ -98,7 +99,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
for (var j = colorStartX; j < colorEndX; j++)
|
||||
{
|
||||
var color = colors[((i - colorStartY) * width) + (j - colorStartX)];
|
||||
drawingSession.FillRectangle(new Rect(startPoint, size), color);
|
||||
drawingSession.FillRectangle(startPoint.ToRect(size), color);
|
||||
startPoint.X += PreviewPixelsPerRawPixel;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
|
@ -216,8 +217,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
|
||||
var transform = TargetElement.TransformToVisual(content);
|
||||
var position = transform.TransformPoint(default(Point));
|
||||
_eyedropper.WorkArea = new Rect(position, new Size(TargetElement.ActualWidth, TargetElement.ActualHeight));
|
||||
var position = transform.TransformPoint(default);
|
||||
_eyedropper.WorkArea = position.ToRect(TargetElement.ActualWidth, TargetElement.ActualHeight);
|
||||
if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null)
|
||||
{
|
||||
_eyedropper.XamlRoot = XamlRoot;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Composition;
|
||||
using Windows.UI.Xaml;
|
||||
|
@ -117,7 +118,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
rectKeyframes.Add(new DiscreteObjectKeyFrame
|
||||
{
|
||||
KeyTime = KeyTime.FromTimeSpan(time),
|
||||
Value = new Rect(startPoint, endPoint)
|
||||
Value = startPoint.ToRect(endPoint)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using Windows.UI.Core;
|
||||
|
@ -98,7 +99,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
|
||||
private void ImageCropperThumb_KeyUp(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
var selectedRect = new Rect(new Point(_startX, _startY), new Point(_endX, _endY));
|
||||
var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY));
|
||||
var croppedRect = _inverseImageTransform.TransformBounds(selectedRect);
|
||||
if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height)
|
||||
{
|
||||
|
@ -111,7 +112,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
|
||||
private void ImageCropperThumb_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
|
||||
{
|
||||
var selectedRect = new Rect(new Point(_startX, _startY), new Point(_endX, _endY));
|
||||
var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY));
|
||||
var croppedRect = _inverseImageTransform.TransformBounds(selectedRect);
|
||||
if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height)
|
||||
{
|
||||
|
@ -155,7 +156,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
offsetY = Math.Max(offsetY, _restrictedSelectRect.Y - _startY);
|
||||
}
|
||||
|
||||
var selectedRect = new Rect(new Point(_startX, _startY), new Point(_endX, _endY));
|
||||
var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY));
|
||||
selectedRect.X += offsetX;
|
||||
selectedRect.Y += offsetY;
|
||||
var croppedRect = _inverseImageTransform.TransformBounds(selectedRect);
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
@ -299,7 +300,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
break;
|
||||
}
|
||||
|
||||
return new Rect(startPoint, endPoint);
|
||||
return startPoint.ToRect(endPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Hosting;
|
||||
|
@ -111,7 +112,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
|
||||
var startPoint = new Point(_startX, _startY);
|
||||
var endPoint = new Point(_endX, _endY);
|
||||
var currentSelectedRect = new Rect(startPoint, endPoint);
|
||||
var currentSelectedRect = startPoint.ToRect(endPoint);
|
||||
switch (position)
|
||||
{
|
||||
case ThumbPosition.Top:
|
||||
|
@ -251,7 +252,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
|
||||
var isEffectiveRegion = IsSafePoint(_restrictedSelectRect, startPoint) &&
|
||||
IsSafePoint(_restrictedSelectRect, endPoint);
|
||||
var selectedRect = new Rect(startPoint, endPoint);
|
||||
var selectedRect = startPoint.ToRect(endPoint);
|
||||
if (!isEffectiveRegion)
|
||||
{
|
||||
if (!IsCornerThumb(position) && TryGetContainedRect(_restrictedSelectRect, ref selectedRect))
|
||||
|
@ -268,8 +269,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
selectedRect.Union(CanvasRect);
|
||||
if (selectedRect != CanvasRect)
|
||||
{
|
||||
var croppedRect = _inverseImageTransform.TransformBounds(
|
||||
new Rect(startPoint, endPoint));
|
||||
var croppedRect = _inverseImageTransform.TransformBounds(startPoint.ToRect(endPoint));
|
||||
croppedRect.Intersect(_restrictedCropRect);
|
||||
_currentCroppedRect = croppedRect;
|
||||
var viewportRect = GetUniformRect(CanvasRect, selectedRect.Width / selectedRect.Height);
|
||||
|
@ -460,7 +460,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
case CropShape.Rectangular:
|
||||
if (_innerGeometry is RectangleGeometry rectangleGeometry)
|
||||
{
|
||||
var to = new Rect(new Point(_startX, _startY), new Point(_endX, _endY));
|
||||
var to = new Point(_startX, _startY).ToRect(new Point(_endX, _endY));
|
||||
if (animate)
|
||||
{
|
||||
var storyboard = new Storyboard();
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Xaml;
|
||||
|
@ -120,7 +120,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
{
|
||||
get
|
||||
{
|
||||
var realMinSelectSize = _imageTransform.TransformBounds(new Rect(default(Point), MinCropSize));
|
||||
var realMinSelectSize = _imageTransform.TransformBounds(MinCropSize.ToRect());
|
||||
var minLength = Math.Min(realMinSelectSize.Width, realMinSelectSize.Height);
|
||||
if (minLength < MinSelectedLength)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Microsoft.Toolkit.Uwp.UI.Extensions;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
|
@ -131,7 +132,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
var y_normalized = (finalSize.Height / 2) - y - (element.DesiredSize.Height / 2);
|
||||
var point = new Point(x_normalized, y_normalized);
|
||||
|
||||
element.Arrange(new Rect(point, element.DesiredSize));
|
||||
element.Arrange(point.ToRect(element.DesiredSize));
|
||||
|
||||
var elementProperties = new OrbitViewElementProperties()
|
||||
{
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
@ -143,7 +143,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
}
|
||||
|
||||
// Set clip to control
|
||||
Clip = new RectangleGeometry() { Rect = new Rect(default(Point), e.NewSize) };
|
||||
Clip = new RectangleGeometry { Rect = e.NewSize.ToRect() };
|
||||
}
|
||||
|
||||
private void RotatorTile_Loaded(object sender, RoutedEventArgs e)
|
||||
|
|
|
@ -152,7 +152,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
|
|||
// Make sure all overflown elements have no size.
|
||||
foreach (var child in _overflow)
|
||||
{
|
||||
child.Arrange(new Rect(0, 0, 0, 0));
|
||||
child.Arrange(default);
|
||||
}
|
||||
|
||||
_overflow = new List<UIElement>(); // Reset for next time.
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Point = Windows.Foundation.Point;
|
||||
using Rect = Windows.Foundation.Rect;
|
||||
using Size = Windows.Foundation.Size;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="Point"/> type.
|
||||
/// </summary>
|
||||
public static class PointExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> of the specified size, starting at a given point.
|
||||
/// </summary>
|
||||
/// <param name="point">The input <see cref="Point"/> value to convert.</param>
|
||||
/// <param name="width">The width of the rectangle.</param>
|
||||
/// <param name="height">The height of the rectangle.</param>
|
||||
/// <returns>A <see cref="Rect"/> value of the specified size, starting at the given point.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Point point, double width, double height)
|
||||
{
|
||||
return new Rect(point.X, point.Y, width, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> ending at the specified point, starting at the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="point">The input <see cref="Point"/> value to convert.</param>
|
||||
/// <param name="end">The ending position for the rectangle.</param>
|
||||
/// <returns>A <see cref="Rect"/> value between the two specified points.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Point point, Point end)
|
||||
{
|
||||
return new Rect(point, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> of the specified size, starting at the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="point">The input <see cref="Point"/> value to convert.</param>
|
||||
/// <param name="size">The size of the rectangle to create.</param>
|
||||
/// <returns>A <see cref="Rect"/> value of the specified size, starting at the given coordinates.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Point point, Size size)
|
||||
{
|
||||
return new Rect(point, size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Point = Windows.Foundation.Point;
|
||||
using Rect = Windows.Foundation.Rect;
|
||||
using Size = Windows.Foundation.Size;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="Size"/> type.
|
||||
/// </summary>
|
||||
public static class SizeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> of the specified size, starting at the origin.
|
||||
/// </summary>
|
||||
/// <param name="size">The input <see cref="Size"/> value to convert.</param>
|
||||
/// <returns>A <see cref="Rect"/> value of the specified size, starting at the origin.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Size size)
|
||||
{
|
||||
return new Rect(0, 0, size.Width, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> of the specified size, starting at the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="size">The input <see cref="Size"/> value to convert.</param>
|
||||
/// <param name="x">The horizontal offset.</param>
|
||||
/// <param name="y">The vertical offset.</param>
|
||||
/// <returns>A <see cref="Rect"/> value of the specified size, starting at the given coordinates.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Size size, double x, double y)
|
||||
{
|
||||
return new Rect(x, y, size.Width, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Rect"/> of the specified size, starting at the given position.
|
||||
/// </summary>
|
||||
/// <param name="size">The input <see cref="Size"/> value to convert.</param>
|
||||
/// <param name="point">The starting position to use.</param>
|
||||
/// <returns>A <see cref="Rect"/> value of the specified size, starting at the given position.</returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Rect ToRect(this Size size, Point point)
|
||||
{
|
||||
return new Rect(point, size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,8 @@ namespace Microsoft.Toolkit.Extensions
|
|||
[typeof(double)] = "double",
|
||||
[typeof(decimal)] = "decimal",
|
||||
[typeof(object)] = "object",
|
||||
[typeof(string)] = "string"
|
||||
[typeof(string)] = "string",
|
||||
[typeof(void)] = "void"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -56,7 +57,7 @@ namespace Microsoft.Toolkit.Extensions
|
|||
public static string ToTypeString(this Type type)
|
||||
{
|
||||
// Local function to create the formatted string for a given type
|
||||
static string FormatDisplayString(Type type)
|
||||
static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan<Type> typeArguments)
|
||||
{
|
||||
// Primitive types use the keyword name
|
||||
if (BuiltInTypesMap.TryGetValue(type, out string? typeName))
|
||||
|
@ -64,62 +65,138 @@ namespace Microsoft.Toolkit.Extensions
|
|||
return typeName!;
|
||||
}
|
||||
|
||||
// Generic types
|
||||
if (
|
||||
#if NETSTANDARD1_4
|
||||
type.GetTypeInfo().IsGenericType &&
|
||||
#else
|
||||
type.IsGenericType &&
|
||||
#endif
|
||||
type.FullName is { } fullName &&
|
||||
fullName.Split('`') is { } tokens &&
|
||||
tokens.Length > 0 &&
|
||||
tokens[0] is { } genericName &&
|
||||
genericName.Length > 0)
|
||||
{
|
||||
var typeArguments = type.GetGenericArguments().Select(FormatDisplayString);
|
||||
|
||||
// Nullable<T> types are displayed as T?
|
||||
var genericType = type.GetGenericTypeDefinition();
|
||||
if (genericType == typeof(Nullable<>))
|
||||
{
|
||||
return $"{typeArguments.First()}?";
|
||||
}
|
||||
|
||||
// ValueTuple<T1, T2> types are displayed as (T1, T2)
|
||||
if (genericType == typeof(ValueTuple<>) ||
|
||||
genericType == typeof(ValueTuple<,>) ||
|
||||
genericType == typeof(ValueTuple<,,>) ||
|
||||
genericType == typeof(ValueTuple<,,,>) ||
|
||||
genericType == typeof(ValueTuple<,,,,>) ||
|
||||
genericType == typeof(ValueTuple<,,,,,>) ||
|
||||
genericType == typeof(ValueTuple<,,,,,,>) ||
|
||||
genericType == typeof(ValueTuple<,,,,,,,>))
|
||||
{
|
||||
return $"({string.Join(", ", typeArguments)})";
|
||||
}
|
||||
|
||||
// Standard generic types are displayed as Foo<T>
|
||||
return $"{genericName}<{string.Join(", ", typeArguments)}>";
|
||||
}
|
||||
|
||||
// Array types are displayed as Foo[]
|
||||
if (type.IsArray)
|
||||
{
|
||||
var elementType = type.GetElementType();
|
||||
var elementType = type.GetElementType()!;
|
||||
var rank = type.GetArrayRank();
|
||||
|
||||
return $"{FormatDisplayString(elementType)}[{new string(',', rank - 1)}]";
|
||||
return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]";
|
||||
}
|
||||
|
||||
return type.ToString();
|
||||
// By checking generic types here we are only interested in specific cases,
|
||||
// ie. nullable value types or value typles. We have a separate path for custom
|
||||
// generic types, as we can't rely on this API in that case, as it doesn't show
|
||||
// a difference between nested types that are themselves generic, or nested simple
|
||||
// types from a generic declaring type. To deal with that, we need to manually track
|
||||
// the offset within the array of generic arguments for the whole constructed type.
|
||||
if (type.IsGenericType())
|
||||
{
|
||||
var genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
// Nullable<T> types are displayed as T?
|
||||
if (genericTypeDefinition == typeof(Nullable<>))
|
||||
{
|
||||
var nullableArguments = type.GetGenericArguments();
|
||||
|
||||
return $"{FormatDisplayString(nullableArguments[0], 0, nullableArguments)}?";
|
||||
}
|
||||
|
||||
// ValueTuple<T1, T2> types are displayed as (T1, T2)
|
||||
if (genericTypeDefinition == typeof(ValueTuple<>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
|
||||
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
|
||||
{
|
||||
var formattedTypes = type.GetGenericArguments().Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
|
||||
|
||||
return $"({string.Join(", ", formattedTypes)})";
|
||||
}
|
||||
}
|
||||
|
||||
string displayName;
|
||||
|
||||
// Generic types
|
||||
if (type.Name.Contains('`'))
|
||||
{
|
||||
// Retrieve the current generic arguments for the current type (leaf or not)
|
||||
var tokens = type.Name.Split('`');
|
||||
var genericArgumentsCount = int.Parse(tokens[1]);
|
||||
var typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
|
||||
var currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
|
||||
var formattedTypes = currentTypeArguments.Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
|
||||
|
||||
// Standard generic types are displayed as Foo<T>
|
||||
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
|
||||
|
||||
// Track the current offset for the shared generic arguments list
|
||||
genericTypeOffset += genericArgumentsCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple custom types
|
||||
displayName = type.Name;
|
||||
}
|
||||
|
||||
// If the type is nested, recursively format the hierarchy as well
|
||||
if (type.IsNested)
|
||||
{
|
||||
var openDeclaringType = type.DeclaringType!;
|
||||
var rootGenericArguments = typeArguments.Slice(0, typeArguments.Length - genericTypeOffset).ToArray();
|
||||
|
||||
// If the declaring type is generic, we need to reconstruct the closed type
|
||||
// manually, as the declaring type instance doesn't retain type information.
|
||||
if (rootGenericArguments.Length > 0)
|
||||
{
|
||||
var closedDeclaringType = openDeclaringType.GetGenericTypeDefinition().MakeGenericType(rootGenericArguments);
|
||||
|
||||
return $"{FormatDisplayString(closedDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(openDeclaringType, genericTypeOffset, typeArguments)}.{displayName}";
|
||||
}
|
||||
|
||||
return $"{type.Namespace}.{displayName}";
|
||||
}
|
||||
|
||||
// Atomically get or build the display string for the current type.
|
||||
// Manually create a static lambda here to enable caching of the generated closure.
|
||||
// This is a workaround for the missing caching for method group conversions, and should
|
||||
// be removed once this issue is resolved: https://github.com/dotnet/roslyn/issues/5835.
|
||||
return DisplayNames.GetValue(type, t => FormatDisplayString(t));
|
||||
return DisplayNames.GetValue(type, t =>
|
||||
{
|
||||
// By-ref types are displayed as T&
|
||||
if (t.IsByRef)
|
||||
{
|
||||
t = t.GetElementType()!;
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}&";
|
||||
}
|
||||
|
||||
// Pointer types are displayed as T*
|
||||
if (t.IsPointer)
|
||||
{
|
||||
int depth = 0;
|
||||
|
||||
// Calculate the pointer indirection level
|
||||
while (t.IsPointer)
|
||||
{
|
||||
depth++;
|
||||
t = t.GetElementType()!;
|
||||
}
|
||||
|
||||
return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}{new string('*', depth)}";
|
||||
}
|
||||
|
||||
// Standard path for concrete types
|
||||
return FormatDisplayString(t, 0, t.GetGenericArguments());
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not a given type is generic.
|
||||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <returns>Whether or not the input type is generic.</returns>
|
||||
[Pure]
|
||||
private static bool IsGenericType(this Type type)
|
||||
{
|
||||
#if NETSTANDARD1_4
|
||||
return type.GetTypeInfo().IsGenericType;
|
||||
#else
|
||||
return type.IsGenericType;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_4
|
||||
|
@ -128,6 +205,7 @@ namespace Microsoft.Toolkit.Extensions
|
|||
/// </summary>
|
||||
/// <param name="type">The input type.</param>
|
||||
/// <returns>An array of types representing the generic arguments.</returns>
|
||||
[Pure]
|
||||
private static Type[] GetGenericArguments(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GenericTypeParameters;
|
||||
|
@ -139,6 +217,7 @@ namespace Microsoft.Toolkit.Extensions
|
|||
/// <param name="type">The input type.</param>
|
||||
/// <param name="value">The type to check against.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="type"/> is an instance of <paramref name="value"/>, <see langword="false"/> otherwise.</returns>
|
||||
[Pure]
|
||||
internal static bool IsInstanceOfType(this Type type, object value)
|
||||
{
|
||||
return type.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo());
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Linq;
|
|||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.Toolkit.HighPerformance.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using UnitTests.HighPerformance.Shared.Buffers;
|
||||
|
||||
namespace UnitTests.HighPerformance.Buffers
|
||||
{
|
||||
|
@ -54,6 +55,61 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
Assert.ThrowsException<ObjectDisposedException>(() => writer.Advance(1));
|
||||
}
|
||||
|
||||
[TestCategory("ArrayPoolBufferWriterOfT")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayPoolBufferWriterOfT_AllocateFromCustomPoolAndGetMemoryAndSpan()
|
||||
{
|
||||
var pool = new TrackingArrayPool<byte>();
|
||||
|
||||
using (var writer = new ArrayPoolBufferWriter<byte>(pool))
|
||||
{
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 1);
|
||||
|
||||
Assert.AreEqual(writer.Capacity, 256);
|
||||
Assert.AreEqual(writer.FreeCapacity, 256);
|
||||
Assert.AreEqual(writer.WrittenCount, 0);
|
||||
Assert.IsTrue(writer.WrittenMemory.IsEmpty);
|
||||
Assert.IsTrue(writer.WrittenSpan.IsEmpty);
|
||||
|
||||
Span<byte> span = writer.GetSpan(43);
|
||||
|
||||
Assert.IsTrue(span.Length >= 43);
|
||||
|
||||
writer.Advance(43);
|
||||
|
||||
Assert.AreEqual(writer.Capacity, 256);
|
||||
Assert.AreEqual(writer.FreeCapacity, 256 - 43);
|
||||
Assert.AreEqual(writer.WrittenCount, 43);
|
||||
Assert.AreEqual(writer.WrittenMemory.Length, 43);
|
||||
Assert.AreEqual(writer.WrittenSpan.Length, 43);
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.Advance(-1));
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => writer.GetMemory(-1));
|
||||
Assert.ThrowsException<ArgumentException>(() => writer.Advance(1024));
|
||||
|
||||
writer.Dispose();
|
||||
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.WrittenMemory);
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.WrittenSpan.Length);
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.Capacity);
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.FreeCapacity);
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.Clear());
|
||||
Assert.ThrowsException<ObjectDisposedException>(() => writer.Advance(1));
|
||||
}
|
||||
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 0);
|
||||
}
|
||||
|
||||
[TestCategory("ArrayPoolBufferWriterOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void Test_ArrayPoolBufferWriterOfT_InvalidRequestedSize()
|
||||
{
|
||||
var writer = new ArrayPoolBufferWriter<byte>(-1);
|
||||
|
||||
Assert.Fail("You shouldn't be here");
|
||||
}
|
||||
|
||||
[TestCategory("ArrayPoolBufferWriterOfT")]
|
||||
[TestMethod]
|
||||
public void Test_ArrayPoolBufferWriterOfT_Clear()
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using UnitTests.HighPerformance.Shared.Buffers;
|
||||
|
||||
namespace UnitTests.HighPerformance.Buffers
|
||||
{
|
||||
|
@ -30,6 +32,39 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
|
||||
}
|
||||
|
||||
[TestCategory("MemoryOwnerOfT")]
|
||||
[TestMethod]
|
||||
public void Test_MemoryOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan()
|
||||
{
|
||||
var pool = new TrackingArrayPool<int>();
|
||||
|
||||
using (var buffer = MemoryOwner<int>.Allocate(127, pool))
|
||||
{
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 1);
|
||||
|
||||
Assert.IsTrue(buffer.Length == 127);
|
||||
Assert.IsTrue(buffer.Memory.Length == 127);
|
||||
Assert.IsTrue(buffer.Span.Length == 127);
|
||||
|
||||
buffer.Span.Fill(42);
|
||||
|
||||
Assert.IsTrue(buffer.Memory.Span.ToArray().All(i => i == 42));
|
||||
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
|
||||
}
|
||||
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 0);
|
||||
}
|
||||
|
||||
[TestCategory("MemoryOwnerOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void Test_MemoryOwnerOfT_InvalidRequestedSize()
|
||||
{
|
||||
using var buffer = MemoryOwner<int>.Allocate(-1);
|
||||
|
||||
Assert.Fail("You shouldn't be here");
|
||||
}
|
||||
|
||||
[TestCategory("MemoryOwnerOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ObjectDisposedException))]
|
||||
|
|
|
@ -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.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.HighPerformance.Buffers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using UnitTests.HighPerformance.Shared.Buffers;
|
||||
|
||||
namespace UnitTests.HighPerformance.Buffers
|
||||
{
|
||||
|
@ -27,6 +29,37 @@ namespace UnitTests.HighPerformance.Buffers
|
|||
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
|
||||
}
|
||||
|
||||
[TestCategory("SpanOwnerOfT")]
|
||||
[TestMethod]
|
||||
public void Test_SpanOwnerOfT_AllocateFromCustomPoolAndGetMemoryAndSpan()
|
||||
{
|
||||
var pool = new TrackingArrayPool<int>();
|
||||
|
||||
using (var buffer = SpanOwner<int>.Allocate(127, pool))
|
||||
{
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 1);
|
||||
|
||||
Assert.IsTrue(buffer.Length == 127);
|
||||
Assert.IsTrue(buffer.Span.Length == 127);
|
||||
|
||||
buffer.Span.Fill(42);
|
||||
|
||||
Assert.IsTrue(buffer.Span.ToArray().All(i => i == 42));
|
||||
}
|
||||
|
||||
Assert.AreEqual(pool.RentedArrays.Count, 0);
|
||||
}
|
||||
|
||||
[TestCategory("SpanOwnerOfT")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void Test_SpanOwnerOfT_InvalidRequestedSize()
|
||||
{
|
||||
using var buffer = SpanOwner<int>.Allocate(-1);
|
||||
|
||||
Assert.Fail("You shouldn't be here");
|
||||
}
|
||||
|
||||
[TestCategory("HashCodeOfT")]
|
||||
[TestMethod]
|
||||
public void Test_SpanOwnerOfT_PooledBuffersAndClear()
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnitTests.HighPerformance.Shared.Buffers
|
||||
{
|
||||
public sealed class TrackingArrayPool<T> : ArrayPool<T>
|
||||
{
|
||||
private readonly ArrayPool<T> pool = ArrayPool<T>.Create();
|
||||
|
||||
private readonly HashSet<T[]> arrays = new HashSet<T[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of currently rented out arrays
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<T[]> RentedArrays => this.arrays;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override T[] Rent(int minimumLength)
|
||||
{
|
||||
T[] array = this.pool.Rent(minimumLength);
|
||||
|
||||
this.arrays.Add(array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Return(T[] array, bool clearArray = false)
|
||||
{
|
||||
this.arrays.Remove(array);
|
||||
|
||||
this.pool.Return(array, clearArray);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_ArrayPoolBufferWriter{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_MemoryOwner{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\Test_SpanOwner{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Buffers\TrackingArrayPool{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.2D.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayPoolExtensions.cs" />
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Toolkit.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
|
@ -15,32 +14,148 @@ namespace UnitTests.Extensions
|
|||
{
|
||||
[TestCategory("TypeExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_TypeExtensions_BuiltInTypes()
|
||||
[DataRow("bool", typeof(bool))]
|
||||
[DataRow("int", typeof(int))]
|
||||
[DataRow("float", typeof(float))]
|
||||
[DataRow("double", typeof(double))]
|
||||
[DataRow("decimal", typeof(decimal))]
|
||||
[DataRow("object", typeof(object))]
|
||||
[DataRow("string", typeof(string))]
|
||||
[DataRow("void", typeof(void))]
|
||||
public void Test_TypeExtensions_BuiltInTypes(string name, Type type)
|
||||
{
|
||||
Assert.AreEqual("bool", typeof(bool).ToTypeString());
|
||||
Assert.AreEqual("int", typeof(int).ToTypeString());
|
||||
Assert.AreEqual("float", typeof(float).ToTypeString());
|
||||
Assert.AreEqual("double", typeof(double).ToTypeString());
|
||||
Assert.AreEqual("decimal", typeof(decimal).ToTypeString());
|
||||
Assert.AreEqual("object", typeof(object).ToTypeString());
|
||||
Assert.AreEqual("string", typeof(string).ToTypeString());
|
||||
Assert.AreEqual(name, type.ToTypeString());
|
||||
}
|
||||
|
||||
[TestCategory("TypeExtensions")]
|
||||
[TestMethod]
|
||||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009", Justification = "Nullable value tuple type")]
|
||||
public void Test_TypeExtensions_GenericTypes()
|
||||
[DataRow("int?", typeof(int?))]
|
||||
[DataRow("System.DateTime?", typeof(DateTime?))]
|
||||
[DataRow("(int, float)", typeof((int, float)))]
|
||||
[DataRow("(double?, string, int)?", typeof((double?, string, int)?))]
|
||||
[DataRow("int[]", typeof(int[]))]
|
||||
[DataRow("int[,]", typeof(int[,]))]
|
||||
[DataRow("System.Span<float>", typeof(Span<float>))]
|
||||
[DataRow("System.Memory<char>", typeof(Memory<char>))]
|
||||
[DataRow("System.Collections.Generic.IEnumerable<int>", typeof(IEnumerable<int>))]
|
||||
[DataRow("System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<float>>", typeof(Dictionary<int, List<float>>))]
|
||||
public void Test_TypeExtensions_GenericTypes(string name, Type type)
|
||||
{
|
||||
Assert.AreEqual(name, type.ToTypeString());
|
||||
}
|
||||
|
||||
[TestCategory("TypeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal", typeof(Animal))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat", typeof(Animal.Cat))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Dog", typeof(Animal.Dog))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int?>", typeof(Animal.Rabbit<int?>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<string>", typeof(Animal.Rabbit<string>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int>.Foo", typeof(Animal.Rabbit<int>.Foo))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<(string, int)?>.Foo", typeof(Animal.Rabbit<(string, int)?>.Foo))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int>.Foo<string>", typeof(Animal.Rabbit<int>.Foo<string>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int>.Foo<int[]>", typeof(Animal.Rabbit<int>.Foo<int[]>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<string[]>.Foo<object>", typeof(Animal.Rabbit<string[]>.Foo<object>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<(string, int)?>.Foo<(int, int?)>", typeof(Animal.Rabbit<(string, int)?>.Foo<(int, int?)>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<float, System.DateTime>", typeof(Animal.Llama<float, DateTime>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<string, (int?, object)>", typeof(Animal.Llama<string, (int?, object)>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<string, (int?, object)?>.Foo", typeof(Animal.Llama<string, (int?, object)?>.Foo))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<float, System.DateTime>.Foo", typeof(Animal.Llama<float, DateTime>.Foo))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<string, (int?, object)?>.Foo<string>", typeof(Animal.Llama<string, (int?, object)?>.Foo<string>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<float, System.DateTime>.Foo<(float?, int)?>", typeof(Animal.Llama<float, DateTime>.Foo<(float?, int)?>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Vehicle<double>", typeof(Vehicle<double>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Vehicle<int?>[]", typeof(Vehicle<int?>[]))]
|
||||
[DataRow("System.Collections.Generic.List<UnitTests.Extensions.Test_TypeExtensions.Vehicle<int>>", typeof(List<Vehicle<int>>))]
|
||||
[DataRow("System.Collections.Generic.List<UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int?>>", typeof(List<Animal.Rabbit<int?>>))]
|
||||
[DataRow("System.Collections.Generic.List<UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<float, System.DateTime[]>>", typeof(List<Animal.Llama<float, DateTime[]>>))]
|
||||
public void Test_TypeExtensions_NestedTypes(string name, Type type)
|
||||
{
|
||||
Assert.AreEqual(name, type.ToTypeString());
|
||||
}
|
||||
|
||||
#pragma warning disable SA1015 // Closing generic brackets should be spaced correctly
|
||||
[TestCategory("TypeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow("void*", typeof(void*))]
|
||||
[DataRow("int**", typeof(int**))]
|
||||
[DataRow("byte***", typeof(byte***))]
|
||||
[DataRow("System.Guid*", typeof(Guid*))]
|
||||
[DataRow("UnitTests.Extensions.Foo<int>*", typeof(Foo<int>*))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat**", typeof(Animal.Cat**))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<int>*", typeof(Animal.Cat<int>*))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<float>.Bar**", typeof(Animal.Cat<float>.Bar**))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<double>.Bar<int>***", typeof(Animal.Cat<double>.Bar<int>***))]
|
||||
public void Test_TypeExtensions_PointerTypes(string name, Type type)
|
||||
{
|
||||
Assert.AreEqual(name, type.ToTypeString());
|
||||
}
|
||||
#pragma warning restore SA1015
|
||||
|
||||
[TestCategory("TypeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow("int&", typeof(int))]
|
||||
[DataRow("byte&", typeof(byte))]
|
||||
[DataRow("System.Guid&", typeof(Guid))]
|
||||
[DataRow("UnitTests.Extensions.Foo<int>&", typeof(Foo<int>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat&", typeof(Animal.Cat))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<int>&", typeof(Animal.Cat<int>))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<float>.Bar&", typeof(Animal.Cat<float>.Bar))]
|
||||
[DataRow("UnitTests.Extensions.Test_TypeExtensions.Animal.Cat<double>.Bar<int>&", typeof(Animal.Cat<double>.Bar<int>))]
|
||||
public void Test_TypeExtensions_RefTypes(string name, Type type)
|
||||
{
|
||||
Assert.AreEqual(name, type.MakeByRefType().ToTypeString());
|
||||
}
|
||||
|
||||
private class Animal
|
||||
{
|
||||
public struct Cat
|
||||
{
|
||||
}
|
||||
|
||||
public struct Cat<T1>
|
||||
{
|
||||
public struct Bar
|
||||
{
|
||||
}
|
||||
|
||||
public struct Bar<T2>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Dog
|
||||
{
|
||||
}
|
||||
|
||||
public class Rabbit<T>
|
||||
{
|
||||
public class Foo
|
||||
{
|
||||
}
|
||||
|
||||
public class Foo<T2>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Llama<T1, T2>
|
||||
{
|
||||
public class Foo
|
||||
{
|
||||
}
|
||||
|
||||
public class Foo<T3>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Vehicle<T>
|
||||
{
|
||||
Assert.AreEqual("int?", typeof(int?).ToTypeString());
|
||||
Assert.AreEqual("System.DateTime?", typeof(DateTime?).ToTypeString());
|
||||
Assert.AreEqual("(int, float)", typeof((int, float)).ToTypeString());
|
||||
Assert.AreEqual("(double?, string, int)?", typeof((double?, string, int)?).ToTypeString());
|
||||
Assert.AreEqual("int[]", typeof(int[]).ToTypeString());
|
||||
Assert.AreEqual(typeof(int[,]).ToTypeString(), "int[,]");
|
||||
Assert.AreEqual("System.Span<float>", typeof(Span<float>).ToTypeString());
|
||||
Assert.AreEqual("System.Memory<char>", typeof(Memory<char>).ToTypeString());
|
||||
Assert.AreEqual("System.Collections.Generic.IEnumerable<int>", typeof(IEnumerable<int>).ToTypeString());
|
||||
Assert.AreEqual(typeof(Dictionary<int, List<float>>).ToTypeString(), "System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<float>>");
|
||||
}
|
||||
}
|
||||
|
||||
internal struct Foo<T>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Mvvm.Input;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.Mvvm
|
||||
{
|
||||
[TestClass]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Generic type")]
|
||||
public class Test_AsyncRelayCommandOfT
|
||||
{
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public async Task Test_AsyncRelayCommandOfT_AlwaysEnabled()
|
||||
{
|
||||
int ticks = 0;
|
||||
|
||||
var command = new AsyncRelayCommand<string>(async s =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
ticks = int.Parse(s);
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
|
||||
Assert.IsTrue(command.CanExecute(null));
|
||||
Assert.IsTrue(command.CanExecute("1"));
|
||||
|
||||
(object, EventArgs) args = default;
|
||||
|
||||
command.CanExecuteChanged += (s, e) => args = (s, e);
|
||||
|
||||
command.NotifyCanExecuteChanged();
|
||||
|
||||
Assert.AreSame(args.Item1, command);
|
||||
Assert.AreSame(args.Item2, EventArgs.Empty);
|
||||
|
||||
Assert.IsNull(command.ExecutionTask);
|
||||
Assert.IsFalse(command.IsRunning);
|
||||
|
||||
Task task = command.ExecuteAsync((object)"42");
|
||||
|
||||
Assert.IsNotNull(command.ExecutionTask);
|
||||
Assert.AreSame(command.ExecutionTask, task);
|
||||
Assert.IsTrue(command.IsRunning);
|
||||
|
||||
await task;
|
||||
|
||||
Assert.IsFalse(command.IsRunning);
|
||||
|
||||
Assert.AreEqual(ticks, 42);
|
||||
|
||||
command.Execute("2");
|
||||
|
||||
await command.ExecutionTask!;
|
||||
|
||||
Assert.AreEqual(ticks, 2);
|
||||
|
||||
Assert.ThrowsException<InvalidCastException>(() => command.Execute(new object()));
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_AsyncRelayCommandOfT_WithCanExecuteFunctionTrue()
|
||||
{
|
||||
int ticks = 0;
|
||||
|
||||
var command = new AsyncRelayCommand<string>(
|
||||
s =>
|
||||
{
|
||||
ticks = int.Parse(s);
|
||||
return Task.CompletedTask;
|
||||
}, s => true);
|
||||
|
||||
Assert.IsTrue(command.CanExecute(null));
|
||||
Assert.IsTrue(command.CanExecute("1"));
|
||||
|
||||
command.Execute("42");
|
||||
|
||||
Assert.AreEqual(ticks, 42);
|
||||
|
||||
command.Execute("2");
|
||||
|
||||
Assert.AreEqual(ticks, 2);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_AsyncRelayCommandOfT_WithCanExecuteFunctionFalse()
|
||||
{
|
||||
int ticks = 0;
|
||||
|
||||
var command = new AsyncRelayCommand<string>(
|
||||
s =>
|
||||
{
|
||||
ticks = int.Parse(s);
|
||||
return Task.CompletedTask;
|
||||
}, s => false);
|
||||
|
||||
Assert.IsFalse(command.CanExecute(null));
|
||||
Assert.IsFalse(command.CanExecute("1"));
|
||||
|
||||
command.Execute("2");
|
||||
|
||||
Assert.AreEqual(ticks, 0);
|
||||
|
||||
command.Execute("42");
|
||||
|
||||
Assert.AreEqual(ticks, 0);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_AsyncRelayCommandOfT_NullWithValueType()
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
var command = new AsyncRelayCommand<int>(i =>
|
||||
{
|
||||
n = i;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Special case for null value types
|
||||
Assert.IsTrue(command.CanExecute(null));
|
||||
|
||||
command = new AsyncRelayCommand<int>(
|
||||
i =>
|
||||
{
|
||||
n = i;
|
||||
return Task.CompletedTask;
|
||||
}, i => i > 0);
|
||||
|
||||
Assert.ThrowsException<NullReferenceException>(() => command.CanExecute(null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,13 +15,18 @@ namespace UnitTests.Mvvm
|
|||
{
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_RequestMessage_Ok()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RequestMessage_Ok(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
object test = null;
|
||||
|
||||
void Receive(NumberRequestMessage m)
|
||||
void Receive(object recipient, NumberRequestMessage m)
|
||||
{
|
||||
test = recipient;
|
||||
|
||||
Assert.IsFalse(m.HasReceivedResponse);
|
||||
|
||||
m.Reply(42);
|
||||
|
@ -33,28 +38,33 @@ namespace UnitTests.Mvvm
|
|||
|
||||
int result = messenger.Send<NumberRequestMessage>();
|
||||
|
||||
Assert.AreSame(test, recipient);
|
||||
Assert.AreEqual(result, 42);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_Messenger_RequestMessage_Fail_NoReply()
|
||||
public void Test_Messenger_RequestMessage_Fail_NoReply(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
|
||||
int result = messenger.Send<NumberRequestMessage>();
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void Test_Messenger_RequestMessage_Fail_MultipleReplies()
|
||||
public void Test_Messenger_RequestMessage_Fail_MultipleReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
void Receive(NumberRequestMessage m)
|
||||
void Receive(object recipient, NumberRequestMessage m)
|
||||
{
|
||||
m.Reply(42);
|
||||
m.Reply(42);
|
||||
|
@ -71,12 +81,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Ok_Sync()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Ok_Sync(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
void Receive(AsyncNumberRequestMessage m)
|
||||
void Receive(object recipient, AsyncNumberRequestMessage m)
|
||||
{
|
||||
Assert.IsFalse(m.HasReceivedResponse);
|
||||
|
||||
|
@ -94,9 +106,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Ok_Async()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Ok_Async(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
async Task<int> GetNumberAsync()
|
||||
|
@ -106,7 +120,7 @@ namespace UnitTests.Mvvm
|
|||
return 42;
|
||||
}
|
||||
|
||||
void Receive(AsyncNumberRequestMessage m)
|
||||
void Receive(object recipient, AsyncNumberRequestMessage m)
|
||||
{
|
||||
Assert.IsFalse(m.HasReceivedResponse);
|
||||
|
||||
|
@ -124,23 +138,27 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Fail_NoReply()
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Fail_NoReply(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
|
||||
int result = await messenger.Send<AsyncNumberRequestMessage>();
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Fail_MultipleReplies()
|
||||
public async Task Test_Messenger_AsyncRequestMessage_Fail_MultipleReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
void Receive(AsyncNumberRequestMessage m)
|
||||
void Receive(object recipient, AsyncNumberRequestMessage m)
|
||||
{
|
||||
m.Reply(42);
|
||||
m.Reply(42);
|
||||
|
@ -157,12 +175,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_CollectionRequestMessage_Ok_NoReplies()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_CollectionRequestMessage_Ok_NoReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
void Receive(NumbersCollectionRequestMessage m)
|
||||
void Receive(object recipient, NumbersCollectionRequestMessage m)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -175,17 +195,36 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_CollectionRequestMessage_Ok_MultipleReplies()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_CollectionRequestMessage_Ok_MultipleReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
object
|
||||
recipient1 = new object(),
|
||||
recipient2 = new object(),
|
||||
recipient3 = new object();
|
||||
recipient3 = new object(),
|
||||
r1 = null,
|
||||
r2 = null,
|
||||
r3 = null;
|
||||
|
||||
void Receive1(NumbersCollectionRequestMessage m) => m.Reply(1);
|
||||
void Receive2(NumbersCollectionRequestMessage m) => m.Reply(2);
|
||||
void Receive3(NumbersCollectionRequestMessage m) => m.Reply(3);
|
||||
void Receive1(object recipient, NumbersCollectionRequestMessage m)
|
||||
{
|
||||
r1 = recipient;
|
||||
m.Reply(1);
|
||||
}
|
||||
|
||||
void Receive2(object recipient, NumbersCollectionRequestMessage m)
|
||||
{
|
||||
r2 = recipient;
|
||||
m.Reply(2);
|
||||
}
|
||||
|
||||
void Receive3(object recipient, NumbersCollectionRequestMessage m)
|
||||
{
|
||||
r3 = recipient;
|
||||
m.Reply(3);
|
||||
}
|
||||
|
||||
messenger.Register<NumbersCollectionRequestMessage>(recipient1, Receive1);
|
||||
messenger.Register<NumbersCollectionRequestMessage>(recipient2, Receive2);
|
||||
|
@ -198,6 +237,10 @@ namespace UnitTests.Mvvm
|
|||
responses.Add(response);
|
||||
}
|
||||
|
||||
Assert.AreSame(r1, recipient1);
|
||||
Assert.AreSame(r2, recipient2);
|
||||
Assert.AreSame(r3, recipient3);
|
||||
|
||||
CollectionAssert.AreEquivalent(responses, new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
|
@ -207,12 +250,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_NoReplies()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_NoReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
void Receive(AsyncNumbersCollectionRequestMessage m)
|
||||
void Receive(object recipient, AsyncNumbersCollectionRequestMessage m)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -225,9 +270,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_MultipleReplies()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_MultipleReplies(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
object
|
||||
recipient1 = new object(),
|
||||
recipient2 = new object(),
|
||||
|
@ -241,10 +288,10 @@ namespace UnitTests.Mvvm
|
|||
return 3;
|
||||
}
|
||||
|
||||
void Receive1(AsyncNumbersCollectionRequestMessage m) => m.Reply(1);
|
||||
void Receive2(AsyncNumbersCollectionRequestMessage m) => m.Reply(Task.FromResult(2));
|
||||
void Receive3(AsyncNumbersCollectionRequestMessage m) => m.Reply(GetNumberAsync());
|
||||
void Receive4(AsyncNumbersCollectionRequestMessage m) => m.Reply(_ => GetNumberAsync());
|
||||
void Receive1(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(1);
|
||||
void Receive2(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(Task.FromResult(2));
|
||||
void Receive3(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(GetNumberAsync());
|
||||
void Receive4(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(_ => GetNumberAsync());
|
||||
|
||||
messenger.Register<AsyncNumbersCollectionRequestMessage>(recipient1, Receive1);
|
||||
messenger.Register<AsyncNumbersCollectionRequestMessage>(recipient2, Receive2);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
|
@ -13,9 +15,11 @@ namespace UnitTests.Mvvm
|
|||
{
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_UnregisterRecipientWithMessageType()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_UnregisterRecipientWithMessageType(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Unregister<MessageA>(recipient);
|
||||
|
@ -23,9 +27,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_UnregisterRecipientWithMessageTypeAndToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_UnregisterRecipientWithMessageTypeAndToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Unregister<MessageA, string>(recipient, nameof(MessageA));
|
||||
|
@ -33,9 +39,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_UnregisterRecipientWithToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_UnregisterRecipientWithToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.UnregisterAll(recipient, nameof(MessageA));
|
||||
|
@ -43,9 +51,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_UnregisterRecipientWithRecipient()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_UnregisterRecipientWithRecipient(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.UnregisterAll(recipient);
|
||||
|
@ -53,12 +63,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageType()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageType(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA>(recipient, m => { });
|
||||
messenger.Register<MessageA>(recipient, (r, m) => { });
|
||||
|
||||
messenger.Unregister<MessageA>(recipient);
|
||||
|
||||
|
@ -67,12 +79,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageTypeAndToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageTypeAndToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), m => { });
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), (r, m) => { });
|
||||
|
||||
messenger.Unregister<MessageA, string>(recipient, nameof(MessageA));
|
||||
|
||||
|
@ -81,12 +95,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), m => { });
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), (r, m) => { });
|
||||
|
||||
messenger.UnregisterAll(recipient, nameof(MessageA));
|
||||
|
||||
|
@ -95,12 +111,14 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithRecipient()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RegisterAndUnregisterRecipientWithRecipient(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), m => { });
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), (r, m) => { });
|
||||
|
||||
messenger.UnregisterAll(recipient);
|
||||
|
||||
|
@ -109,120 +127,145 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithNoToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithNoToken(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
object a = new object();
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
object recipient = null;
|
||||
string result = null;
|
||||
|
||||
messenger.Register<MessageA>(a, (r, m) =>
|
||||
{
|
||||
recipient = r;
|
||||
result = m.Text;
|
||||
});
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) });
|
||||
|
||||
Assert.AreSame(recipient, a);
|
||||
Assert.AreEqual(result, nameof(MessageA));
|
||||
|
||||
messenger.Unregister<MessageA>(a);
|
||||
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
recipient = null;
|
||||
result = null;
|
||||
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) });
|
||||
|
||||
Assert.IsNull(recipient);
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterRecipient_WithNoToken(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
object a = new object();
|
||||
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
string result = null;
|
||||
Messenger.Default.Register<MessageA>(a, m => result = m.Text);
|
||||
messenger.Register<MessageA>(a, (r, m) => result = m.Text);
|
||||
|
||||
Assert.IsTrue(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) });
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) });
|
||||
|
||||
Assert.AreEqual(result, nameof(MessageA));
|
||||
|
||||
Messenger.Default.Unregister<MessageA>(a);
|
||||
messenger.UnregisterAll(a);
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
result = null;
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) });
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) });
|
||||
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterRecipient_WithNoToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
object a = new object();
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(a));
|
||||
|
||||
string result = null;
|
||||
Messenger.Default.Register<MessageA>(a, m => result = m.Text);
|
||||
messenger.Register<MessageA, string>(a, nameof(MessageA), (r, m) => result = m.Text);
|
||||
|
||||
Assert.IsTrue(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA, string>(a, nameof(MessageA)));
|
||||
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) });
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA));
|
||||
|
||||
Assert.AreEqual(result, nameof(MessageA));
|
||||
|
||||
Messenger.Default.UnregisterAll(a);
|
||||
messenger.Unregister<MessageA, string>(a, nameof(MessageA));
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA, string>(a, nameof(MessageA)));
|
||||
|
||||
result = null;
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) });
|
||||
messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA));
|
||||
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_DuplicateRegistrationWithMessageType(Type type)
|
||||
{
|
||||
object a = new object();
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA>(a));
|
||||
|
||||
string result = null;
|
||||
Messenger.Default.Register<MessageA, string>(a, nameof(MessageA), m => result = m.Text);
|
||||
|
||||
Assert.IsTrue(Messenger.Default.IsRegistered<MessageA, string>(a, nameof(MessageA)));
|
||||
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA));
|
||||
|
||||
Assert.AreEqual(result, nameof(MessageA));
|
||||
|
||||
Messenger.Default.Unregister<MessageA, string>(a, nameof(MessageA));
|
||||
|
||||
Assert.IsFalse(Messenger.Default.IsRegistered<MessageA, string>(a, nameof(MessageA)));
|
||||
|
||||
result = null;
|
||||
Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA));
|
||||
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_DuplicateRegistrationWithMessageType()
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA>(recipient, m => { });
|
||||
messenger.Register<MessageA>(recipient, (r, m) => { });
|
||||
|
||||
Assert.ThrowsException<InvalidOperationException>(() =>
|
||||
{
|
||||
messenger.Register<MessageA>(recipient, m => { });
|
||||
messenger.Register<MessageA>(recipient, (r, m) => { });
|
||||
});
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_DuplicateRegistrationWithMessageTypeAndToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_DuplicateRegistrationWithMessageTypeAndToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new object();
|
||||
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), m => { });
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), (r, m) => { });
|
||||
|
||||
Assert.ThrowsException<InvalidOperationException>(() =>
|
||||
{
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), m => { });
|
||||
messenger.Register<MessageA, string>(recipient, nameof(MessageA), (r, m) => { });
|
||||
});
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IRecipient_NoMessages()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IRecipient_NoMessages(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithNoMessages();
|
||||
|
||||
messenger.RegisterAll(recipient);
|
||||
|
@ -233,9 +276,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IRecipient_SomeMessages_NoToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IRecipient_SomeMessages_NoToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithSomeMessages();
|
||||
|
||||
messenger.RegisterAll(recipient);
|
||||
|
@ -264,9 +309,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_Messenger_IRecipient_SomeMessages_WithToken()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_IRecipient_SomeMessages_WithToken(Type type)
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithSomeMessages();
|
||||
var token = nameof(Test_Messenger_IRecipient_SomeMessages_WithToken);
|
||||
|
||||
|
@ -297,12 +344,182 @@ namespace UnitTests.Mvvm
|
|||
Assert.IsFalse(messenger.IsRegistered<MessageB>(recipient));
|
||||
}
|
||||
|
||||
public sealed class RecipientWithNoMessages
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_RegisterWithTypeParameter(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithNoMessages { Number = 42 };
|
||||
|
||||
int number = 0;
|
||||
|
||||
messenger.Register<RecipientWithNoMessages, MessageA>(recipient, (r, m) => number = r.Number);
|
||||
|
||||
messenger.Send<MessageA>();
|
||||
|
||||
Assert.AreEqual(number, 42);
|
||||
}
|
||||
|
||||
public sealed class RecipientWithSomeMessages
|
||||
: IRecipient<MessageA>, ICloneable, IRecipient<MessageB>
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger), false)]
|
||||
[DataRow(typeof(WeakReferenceMessenger), true)]
|
||||
public void Test_Messenger_Collect_Test(Type type, bool isWeak)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
|
||||
WeakReference weakRecipient;
|
||||
|
||||
void Test()
|
||||
{
|
||||
var recipient = new RecipientWithNoMessages { Number = 42 };
|
||||
weakRecipient = new WeakReference(recipient);
|
||||
|
||||
messenger.Register<MessageA>(recipient, (r, m) => { });
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(recipient));
|
||||
Assert.IsTrue(weakRecipient.IsAlive);
|
||||
|
||||
GC.KeepAlive(recipient);
|
||||
}
|
||||
|
||||
Test();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
Assert.AreEqual(!isWeak, weakRecipient.IsAlive);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_Reset(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithSomeMessages();
|
||||
|
||||
messenger.RegisterAll(recipient);
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(recipient));
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageB>(recipient));
|
||||
|
||||
messenger.Reset();
|
||||
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageA>(recipient));
|
||||
Assert.IsFalse(messenger.IsRegistered<MessageB>(recipient));
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_Default(Type type)
|
||||
{
|
||||
PropertyInfo defaultInfo = type.GetProperty("Default");
|
||||
|
||||
var default1 = defaultInfo!.GetValue(null);
|
||||
var default2 = defaultInfo!.GetValue(null);
|
||||
|
||||
Assert.IsNotNull(default1);
|
||||
Assert.IsNotNull(default2);
|
||||
Assert.AreSame(default1, default2);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_Cleanup(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var recipient = new RecipientWithSomeMessages();
|
||||
|
||||
messenger.Register<MessageA>(recipient);
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(recipient));
|
||||
|
||||
void Test()
|
||||
{
|
||||
var recipient2 = new RecipientWithSomeMessages();
|
||||
|
||||
messenger.Register<MessageB>(recipient2);
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageB>(recipient2));
|
||||
|
||||
GC.KeepAlive(recipient2);
|
||||
}
|
||||
|
||||
Test();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
// Here we just check that calling Cleanup doesn't alter the state
|
||||
// of the messenger. This method shouldn't really do anything visible
|
||||
// to consumers, it's just a way for messengers to compact their data.
|
||||
messenger.Cleanup();
|
||||
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(recipient));
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_Messenger_ManyRecipients(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
|
||||
void Test()
|
||||
{
|
||||
var recipients = Enumerable.Range(0, 512).Select(_ => new RecipientWithSomeMessages()).ToArray();
|
||||
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
messenger.RegisterAll(recipient);
|
||||
}
|
||||
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageA>(recipient));
|
||||
Assert.IsTrue(messenger.IsRegistered<MessageB>(recipient));
|
||||
}
|
||||
|
||||
messenger.Send<MessageA>();
|
||||
messenger.Send<MessageB>();
|
||||
messenger.Send<MessageB>();
|
||||
|
||||
foreach (var recipient in recipients)
|
||||
{
|
||||
Assert.AreEqual(recipient.As, 1);
|
||||
Assert.AreEqual(recipient.Bs, 2);
|
||||
}
|
||||
|
||||
foreach (ref var recipient in recipients.AsSpan())
|
||||
{
|
||||
recipient = null;
|
||||
}
|
||||
}
|
||||
|
||||
Test();
|
||||
|
||||
GC.Collect();
|
||||
|
||||
// Just invoke a final cleanup to improve coverage, this is unrelated to this test in particular
|
||||
messenger.Cleanup();
|
||||
}
|
||||
|
||||
public sealed class RecipientWithNoMessages
|
||||
{
|
||||
public int Number { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RecipientWithSomeMessages :
|
||||
IRecipient<MessageA>,
|
||||
IRecipient<MessageB>,
|
||||
ICloneable
|
||||
{
|
||||
public int As { get; private set; }
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Toolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging.Messages;
|
||||
|
@ -14,9 +15,12 @@ namespace UnitTests.Mvvm
|
|||
{
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_ObservableRecipient_Activation()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_ObservableRecipient_Activation(Type type)
|
||||
{
|
||||
var viewmodel = new SomeRecipient<int>();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var viewmodel = new SomeRecipient<int>(messenger);
|
||||
|
||||
Assert.IsFalse(viewmodel.IsActivatedCheck);
|
||||
|
||||
|
@ -33,18 +37,11 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_ObservableRecipient_Defaults()
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_ObservableRecipient_IsSame(Type type)
|
||||
{
|
||||
var viewmodel = new SomeRecipient<int>();
|
||||
|
||||
Assert.AreSame(viewmodel.CurrentMessenger, Messenger.Default);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_ObservableRecipient_Injection()
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var viewmodel = new SomeRecipient<int>(messenger);
|
||||
|
||||
Assert.AreSame(viewmodel.CurrentMessenger, messenger);
|
||||
|
@ -52,14 +49,37 @@ namespace UnitTests.Mvvm
|
|||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_ObservableRecipient_Broadcast()
|
||||
public void Test_ObservableRecipient_Default()
|
||||
{
|
||||
var messenger = new Messenger();
|
||||
var viewmodel = new SomeRecipient<int>();
|
||||
|
||||
Assert.AreSame(viewmodel.CurrentMessenger, WeakReferenceMessenger.Default);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_ObservableRecipient_Injection(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var viewmodel = new SomeRecipient<int>(messenger);
|
||||
|
||||
Assert.AreSame(viewmodel.CurrentMessenger, messenger);
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
[DataRow(typeof(StrongReferenceMessenger))]
|
||||
[DataRow(typeof(WeakReferenceMessenger))]
|
||||
public void Test_ObservableRecipient_Broadcast(Type type)
|
||||
{
|
||||
var messenger = (IMessenger)Activator.CreateInstance(type);
|
||||
var viewmodel = new SomeRecipient<int>(messenger);
|
||||
|
||||
PropertyChangedMessage<int> message = null;
|
||||
|
||||
messenger.Register<PropertyChangedMessage<int>>(messenger, m => message = m);
|
||||
messenger.Register<PropertyChangedMessage<int>>(messenger, (r, m) => message = m);
|
||||
|
||||
viewmodel.Data = 42;
|
||||
|
||||
|
@ -97,7 +117,7 @@ namespace UnitTests.Mvvm
|
|||
{
|
||||
IsActivatedCheck = true;
|
||||
|
||||
Messenger.Register<SampleMessage>(this, m => { });
|
||||
Messenger.Register<SampleMessage>(this, (r, m) => { });
|
||||
}
|
||||
|
||||
protected override void OnDeactivated()
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace UnitTests.Mvvm
|
|||
Assert.AreSame(args.Item1, command);
|
||||
Assert.AreSame(args.Item2, EventArgs.Empty);
|
||||
|
||||
command.Execute("Hello");
|
||||
command.Execute((object)"Hello");
|
||||
|
||||
Assert.AreEqual(text, "Hello");
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace UnitTests.Mvvm
|
|||
|
||||
Assert.ThrowsException<InvalidCastException>(() => command.CanExecute(new object()));
|
||||
|
||||
command.Execute("Hello");
|
||||
command.Execute((object)"Hello");
|
||||
|
||||
Assert.AreEqual(text, "Hello");
|
||||
|
||||
|
@ -65,5 +65,21 @@ namespace UnitTests.Mvvm
|
|||
|
||||
Assert.AreEqual(text, "Hello");
|
||||
}
|
||||
|
||||
[TestCategory("Mvvm")]
|
||||
[TestMethod]
|
||||
public void Test_RelayCommand_NullWithValueType()
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
var command = new RelayCommand<int>(i => n = i);
|
||||
|
||||
// Special case for null value types
|
||||
Assert.IsTrue(command.CanExecute(null));
|
||||
|
||||
command = new RelayCommand<int>(i => n = i, i => i > 0);
|
||||
|
||||
Assert.ThrowsException<NullReferenceException>(() => command.CanExecute(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_ObservableValidator.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_ObservableObject.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_AsyncRelayCommand.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_AsyncRelayCommand{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_RelayCommand.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_RelayCommand{T}.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Mvvm\Test_ObservableRecipient.cs" />
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// 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 Windows.Foundation;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_PointExtensions
|
||||
{
|
||||
[TestCategory("PointExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d, 0, 0)]
|
||||
[DataRow(0d, 0d, 22, 6.89d)]
|
||||
[DataRow(3.14d, 6.55f, 3838d, 3.24724928d)]
|
||||
[DataRow(double.MinValue, double.Epsilon, 22, 0.3248d)]
|
||||
public static void Test_PointExtensions_ToRect_FromWidthHeight(double width, double height, int x, int y)
|
||||
{
|
||||
Point p = new Point(x, y);
|
||||
Rect
|
||||
a = p.ToRect(width, height),
|
||||
b = new Rect(x, y, width, height);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
|
||||
[TestCategory("SizeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d, 0, 0)]
|
||||
[DataRow(0d, 0d, 22, 6.89d)]
|
||||
[DataRow(3.14d, 6.55f, 3838d, 3.24724928d)]
|
||||
[DataRow(double.MinValue, double.Epsilon, 22, 0.3248d)]
|
||||
public static void Test_PointExtensions_ToRect_FromPoint(double width, double height, int x, int y)
|
||||
{
|
||||
Point
|
||||
p1 = new Point(x, y),
|
||||
p2 = new Point(x + width, y + height);
|
||||
Rect
|
||||
a = p1.ToRect(p2),
|
||||
b = new Rect(p1, p2);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
|
||||
[TestCategory("SizeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d, 0, 0)]
|
||||
[DataRow(0d, 0d, 22, 6.89d)]
|
||||
[DataRow(3.14d, 6.55f, 3838d, 3.24724928d)]
|
||||
[DataRow(double.MinValue, double.Epsilon, 22, 0.3248d)]
|
||||
public static void Test_PointExtensions_ToRect_FromSize(double width, double height, int x, int y)
|
||||
{
|
||||
Point p = new Point(x, y);
|
||||
Size s = new Size(width, height);
|
||||
Rect
|
||||
a = p.ToRect(s),
|
||||
b = new Rect(p, s);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// 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 Windows.Foundation;
|
||||
using Microsoft.Toolkit.Uwp.Extensions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests.Extensions
|
||||
{
|
||||
[TestClass]
|
||||
public class Test_SizeExtensions
|
||||
{
|
||||
[TestCategory("SizeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d)]
|
||||
[DataRow(3.14d, 6.55f)]
|
||||
[DataRow(double.MinValue, double.Epsilon)]
|
||||
public static void Test_SizeExtensions_ToRect_FromSize(double width, double height)
|
||||
{
|
||||
Size s = new Size(width, height);
|
||||
Rect
|
||||
a = s.ToRect(),
|
||||
b = new Rect(0, 0, s.Width, s.Height);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
|
||||
[TestCategory("SizeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d, 0, 0)]
|
||||
[DataRow(0d, 0d, 22, 6.89d)]
|
||||
[DataRow(3.14d, 6.55f, 3838d, 3.24724928d)]
|
||||
[DataRow(double.MinValue, double.Epsilon, 22, 0.3248d)]
|
||||
public static void Test_SizeExtensions_ToRect_FromSizeAndPosition(double width, double height, int x, int y)
|
||||
{
|
||||
Size s = new Size(width, height);
|
||||
Rect
|
||||
a = s.ToRect(x, y),
|
||||
b = new Rect(x, y, s.Width, s.Height);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
|
||||
[TestCategory("SizeExtensions")]
|
||||
[TestMethod]
|
||||
[DataRow(0d, 0d, 0, 0)]
|
||||
[DataRow(0d, 0d, 22, 6.89d)]
|
||||
[DataRow(3.14d, 6.55f, 3838d, 3.24724928d)]
|
||||
[DataRow(double.MinValue, double.Epsilon, 22, 0.3248d)]
|
||||
public static void Test_SizeExtensions_ToRect_FromSizeAndPoint(double width, double height, int x, int y)
|
||||
{
|
||||
Point p = new Point(x, y);
|
||||
Size s = new Size(width, height);
|
||||
Rect
|
||||
a = s.ToRect(p),
|
||||
b = new Rect(p, s);
|
||||
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,6 +138,8 @@
|
|||
<Compile Include="Converters\Test_StringFormatConverter.cs" />
|
||||
<Compile Include="Converters\Test_TypeToObjectConverter.cs" />
|
||||
<Compile Include="Extensions\Helpers\ObjectWithNullableBoolProperty.cs" />
|
||||
<Compile Include="Extensions\Test_PointExtensions.cs" />
|
||||
<Compile Include="Extensions\Test_SizeExtensions.cs" />
|
||||
<Compile Include="Extensions\Test_BitmapIconExtensionMarkupExtension.cs" />
|
||||
<Compile Include="Extensions\Test_FontIconSourceExtensionMarkupExtension.cs" />
|
||||
<Compile Include="Extensions\Test_FontIconExtensionMarkupExtension.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче