mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2025-04-13 20:15:45 +02:00
Merge branch 'master' into improvement/high-performance-tweaks
This commit is contained in:
commit
90c63872e4
Microsoft.Toolkit.Mvvm
Microsoft.Toolkit
UnitTests/UnitTests.Shared
Collections
Diagnostics
Extensions
@ -66,7 +66,26 @@ protected virtual void OnPropertyChanging([CallerMemberName] string? propertyNam
|
||||
/// </remarks>
|
||||
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, propertyName);
|
||||
// We duplicate the code here instead of calling the overload because we can't
|
||||
// guarantee that the invoked SetProperty<T> will be inlined, and we need the JIT
|
||||
// to be able to see the full EqualityComparer<T>.Default.Equals call, so that
|
||||
// it'll use the intrinsics version of it and just replace the whole invocation
|
||||
// with a direct comparison when possible (eg. for primitive numeric types).
|
||||
// This is the fastest SetProperty<T> overload so we particularly care about
|
||||
// the codegen quality here, and the code is small and simple enough so that
|
||||
// duplicating it still doesn't make the whole class harder to maintain.
|
||||
if (EqualityComparer<T>.Default.Equals(field, newValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OnPropertyChanging(propertyName);
|
||||
|
||||
field = newValue;
|
||||
|
||||
OnPropertyChanged(propertyName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -203,7 +222,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
|
||||
/// </remarks>
|
||||
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, propertyName);
|
||||
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, out _, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -220,6 +239,21 @@ protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return SetProperty(propertyExpression, newValue, comparer, out _, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the shared logic for <see cref="SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of property to set.</typeparam>
|
||||
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
|
||||
/// <param name="newValue">The property's value after the change occurred.</param>
|
||||
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
|
||||
/// <param name="oldValue">The resulting initial value for the target property.</param>
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
private protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, out T oldValue, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyInfo? parentPropertyInfo;
|
||||
FieldInfo? parentFieldInfo = null;
|
||||
@ -236,13 +270,15 @@ parentExpression.Expression is ConstantExpression instanceExpression &&
|
||||
ThrowArgumentExceptionForInvalidPropertyExpression();
|
||||
|
||||
// This is never executed, as the method above always throws
|
||||
oldValue = default!;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
object parent = parentPropertyInfo is null
|
||||
? parentFieldInfo!.GetValue(instance)
|
||||
: parentPropertyInfo.GetValue(instance);
|
||||
T oldValue = (T)targetPropertyInfo.GetValue(parent);
|
||||
oldValue = (T)targetPropertyInfo.GetValue(parent);
|
||||
|
||||
if (comparer.Equals(oldValue, newValue))
|
||||
{
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging;
|
||||
using Microsoft.Toolkit.Mvvm.Messaging.Messages;
|
||||
@ -140,7 +141,18 @@ protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName
|
||||
/// </remarks>
|
||||
protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
|
||||
T oldValue = field;
|
||||
|
||||
// We duplicate the code as in the base class here to leverage
|
||||
// the intrinsics support for EqualityComparer<T>.Default.Equals.
|
||||
bool propertyChanged = SetProperty(ref field, newValue, propertyName);
|
||||
|
||||
if (propertyChanged && broadcast)
|
||||
{
|
||||
Broadcast(oldValue, newValue, propertyName);
|
||||
}
|
||||
|
||||
return propertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -158,21 +170,16 @@ protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMe
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!broadcast)
|
||||
{
|
||||
return SetProperty(ref field, newValue, comparer, propertyName);
|
||||
}
|
||||
|
||||
T oldValue = field;
|
||||
|
||||
if (SetProperty(ref field, newValue, comparer, propertyName))
|
||||
bool propertyChanged = SetProperty(ref field, newValue, comparer, propertyName);
|
||||
|
||||
if (propertyChanged && broadcast)
|
||||
{
|
||||
Broadcast(oldValue, newValue, propertyName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return propertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -216,19 +223,61 @@ protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool b
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!broadcast)
|
||||
{
|
||||
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
|
||||
}
|
||||
bool propertyChanged = SetProperty(oldValue, newValue, comparer, callback, propertyName);
|
||||
|
||||
if (SetProperty(oldValue, newValue, comparer, callback, propertyName))
|
||||
if (propertyChanged && broadcast)
|
||||
{
|
||||
Broadcast(oldValue, newValue, propertyName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return propertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the current and new values for a given nested property. If the value has changed,
|
||||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
|
||||
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
|
||||
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>, with the difference being that this
|
||||
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
|
||||
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of property to set.</typeparam>
|
||||
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
|
||||
/// <param name="newValue">The property's value after the change occurred.</param>
|
||||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the current and new values for a given nested property. If the value has changed,
|
||||
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
|
||||
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
|
||||
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>,
|
||||
/// with the difference being that this method is used to relay properties from a wrapped model in the
|
||||
/// current instance. For more info, see the docs for
|
||||
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of property to set.</typeparam>
|
||||
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
|
||||
/// <param name="newValue">The property's value after the change occurred.</param>
|
||||
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
|
||||
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
|
||||
/// <param name="propertyName">(optional) The name of the property that changed.</param>
|
||||
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
|
||||
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
bool propertyChanged = SetProperty(propertyExpression, newValue, comparer, out T oldValue, propertyName);
|
||||
|
||||
if (propertyChanged && broadcast)
|
||||
{
|
||||
Broadcast(oldValue, newValue, propertyName);
|
||||
}
|
||||
|
||||
return propertyChanged;
|
||||
}
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ public sealed class Ioc : IServiceProvider
|
||||
/// <summary>
|
||||
/// The <see cref="ServiceProvider"/> instance to use, if initialized.
|
||||
/// </summary>
|
||||
private ServiceProvider? serviceProvider;
|
||||
private volatile ServiceProvider? serviceProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
object? IServiceProvider.GetService(Type serviceType)
|
||||
|
@ -638,9 +638,14 @@ public Type2(Type tMessage, Type tToken)
|
||||
[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
|
||||
ReferenceEquals(this.tMessage, other.tMessage) &&
|
||||
ReferenceEquals(this.tToken, other.tToken);
|
||||
this.tMessage == other.tMessage &&
|
||||
this.tToken == other.tToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -17,9 +17,37 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
|
||||
public static partial class MessengerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
|
||||
/// 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
|
||||
/// 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>
|
||||
private static readonly MethodInfo RegisterIRecipientMethodInfo;
|
||||
private static class MethodInfos
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="MethodInfos"/> class.
|
||||
/// </summary>
|
||||
static MethodInfos()
|
||||
{
|
||||
RegisterIRecipient = (
|
||||
from methodInfo in typeof(MessengerExtensions).GetMethods()
|
||||
where methodInfo.Name == nameof(Register) &&
|
||||
methodInfo.IsGenericMethod &&
|
||||
methodInfo.GetGenericArguments().Length == 2
|
||||
let parameters = methodInfo.GetParameters()
|
||||
where parameters.Length == 3 &&
|
||||
parameters[1].ParameterType.IsGenericType &&
|
||||
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
|
||||
select methodInfo).First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
|
||||
/// </summary>
|
||||
public static readonly MethodInfo RegisterIRecipient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class that acts as a static container to associate a <see cref="ConditionalWeakTable{TKey,TValue}"/> instance to each
|
||||
@ -39,23 +67,6 @@ public static readonly ConditionalWeakTable<Type, Action<IMessenger, object, TTo
|
||||
= new ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="MessengerExtensions"/> class.
|
||||
/// </summary>
|
||||
static MessengerExtensions()
|
||||
{
|
||||
RegisterIRecipientMethodInfo = (
|
||||
from methodInfo in typeof(MessengerExtensions).GetMethods()
|
||||
where methodInfo.Name == nameof(Register) &&
|
||||
methodInfo.IsGenericMethod &&
|
||||
methodInfo.GetGenericArguments().Length == 2
|
||||
let parameters = methodInfo.GetParameters()
|
||||
where parameters.Length == 3 &&
|
||||
parameters[1].ParameterType.IsGenericType &&
|
||||
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
|
||||
select methodInfo).First();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not a given recipient has already been registered for a message.
|
||||
/// </summary>
|
||||
@ -110,7 +121,7 @@ from interfaceType in type.GetInterfaces()
|
||||
where interfaceType.IsGenericType &&
|
||||
interfaceType.GetGenericTypeDefinition() == typeof(IRecipient<>)
|
||||
let messageType = interfaceType.GenericTypeArguments[0]
|
||||
let registrationMethod = RegisterIRecipientMethodInfo.MakeGenericMethod(messageType, typeof(TToken))
|
||||
let registrationMethod = MethodInfos.RegisterIRecipient.MakeGenericMethod(messageType, typeof(TToken))
|
||||
let registrationAction = GetRegistrationAction(type, registrationMethod)
|
||||
select registrationAction).ToArray();
|
||||
}
|
||||
|
@ -4,16 +4,16 @@
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<Title>Windows Community Toolkit Mvvm .NET Standard</Title>
|
||||
<Title>Windows Community Toolkit MVVM Toolkit</Title>
|
||||
<Description>
|
||||
This package includes Mvvm .NET Standard code only helpers such as:
|
||||
This package includes a .NET Standard MVVM library with helpers such as:
|
||||
- ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.
|
||||
- ObservableRecipient: a base class for observable objects with support for the IMessenger service.
|
||||
- RelayCommand: a simple delegate command implementing the ICommand interface.
|
||||
- Messenger: a messaging system to exchange messages through different loosely-coupled objects.
|
||||
- Ioc: a helper class to configure dependency injection service containers.
|
||||
</Description>
|
||||
<PackageTags>UWP Toolkit Windows Mvvm observable Ioc dependency injection services extensions helpers</PackageTags>
|
||||
<PackageTags>UWP Toolkit Windows MVVM MVVMToolkit observable Ioc dependency injection services extensions helpers</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- .NET Standard 2.0 doesn't have the Span<T> type -->
|
||||
|
@ -27,7 +27,7 @@ public static class StringExtensions
|
||||
/// <summary>
|
||||
/// Regular expression for matching an email address.
|
||||
/// </summary>
|
||||
/// <remarks>General Email Regex (RFC 5322 Official Standard) from emailregex.com.</remarks>
|
||||
/// <remarks>General Email Regex (RFC 5322 Official Standard) from https://emailregex.com.</remarks>
|
||||
internal const string EmailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
|
||||
/// <summary>
|
||||
|
@ -2,7 +2,6 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Reflection;
|
||||
@ -41,28 +40,21 @@ public static class TaskExtensions
|
||||
#endif
|
||||
)
|
||||
{
|
||||
Type taskType = task.GetType();
|
||||
|
||||
// Check if the task is actually some Task<T>
|
||||
if (
|
||||
// Try to get the Task<T>.Result property. This method would've
|
||||
// been called anyway after the type checks, but using that to
|
||||
// validate the input type saves some additional reflection calls.
|
||||
// Furthermore, doing this also makes the method flexible enough to
|
||||
// cases whether the input Task<T> is actually an instance of some
|
||||
// runtime-specific type that inherits from Task<T>.
|
||||
PropertyInfo? propertyInfo =
|
||||
#if NETSTANDARD1_4
|
||||
taskType.GetTypeInfo().IsGenericType &&
|
||||
task.GetType().GetRuntimeProperty(nameof(Task<object>.Result));
|
||||
#else
|
||||
taskType.IsGenericType &&
|
||||
#endif
|
||||
taskType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
// Get the Task<T>.Result property
|
||||
PropertyInfo propertyInfo =
|
||||
#if NETSTANDARD1_4
|
||||
taskType.GetRuntimeProperty(nameof(Task<object>.Result));
|
||||
#else
|
||||
taskType.GetProperty(nameof(Task<object>.Result));
|
||||
task.GetType().GetProperty(nameof(Task<object>.Result));
|
||||
#endif
|
||||
|
||||
// Finally retrieve the result
|
||||
return propertyInfo!.GetValue(task);
|
||||
}
|
||||
// Return the result, if possible
|
||||
return propertyInfo?.GetValue(task);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -9,7 +9,7 @@
|
||||
namespace Microsoft.Toolkit.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to wrap around a Task to provide more information usable for UI databinding scenarios. As discussed in MSDN Magazine: https://msdn.microsoft.com/magazine/dn605875.
|
||||
/// Helper class to wrap around a Task to provide more information usable for UI data binding scenarios. As discussed in MSDN Magazine: https://msdn.microsoft.com/magazine/dn605875.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">Type of result returned by task.</typeparam>
|
||||
[Obsolete("This helper will be removed in a future release, use the ObservableObject base class from Microsoft.Toolkit.Mvvm and the SetAndNotifyOnCompletion method")]
|
||||
|
@ -284,7 +284,7 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis
|
||||
|
||||
[TestCategory("Collections")]
|
||||
[TestMethod]
|
||||
public void InsertItem_WhenGroupDoesNotExist_ShoudThrow()
|
||||
public void InsertItem_WhenGroupDoesNotExist_ShouldThrow()
|
||||
{
|
||||
var groupedCollection = new ObservableGroupedCollection<string, int>();
|
||||
groupedCollection.AddGroup("A", 1, 2, 3);
|
||||
@ -298,7 +298,7 @@ public void InsertItem_WhenGroupDoesNotExist_ShoudThrow()
|
||||
[DataTestMethod]
|
||||
[DataRow(-1)]
|
||||
[DataRow(4)]
|
||||
public void InsertItem_WhenIndexOutOfRange_ShoudThrow(int index)
|
||||
public void InsertItem_WhenIndexOutOfRange_ShouldThrow(int index)
|
||||
{
|
||||
var groupedCollection = new ObservableGroupedCollection<string, int>();
|
||||
groupedCollection.AddGroup("A", 1, 2, 3);
|
||||
@ -340,7 +340,7 @@ public void InsertItem_WithValidIndex_WithSeveralGroups_ShoudInsertItemInFirstGr
|
||||
|
||||
[TestCategory("Collections")]
|
||||
[TestMethod]
|
||||
public void SetItem_WhenGroupDoesNotExist_ShoudThrow()
|
||||
public void SetItem_WhenGroupDoesNotExist_ShouldThrow()
|
||||
{
|
||||
var groupedCollection = new ObservableGroupedCollection<string, int>();
|
||||
groupedCollection.AddGroup("A", 1, 2, 3);
|
||||
@ -354,7 +354,7 @@ public void SetItem_WhenGroupDoesNotExist_ShoudThrow()
|
||||
[DataTestMethod]
|
||||
[DataRow(-1)]
|
||||
[DataRow(3)]
|
||||
public void SetItem_WhenIndexOutOfRange_ShoudThrow(int index)
|
||||
public void SetItem_WhenIndexOutOfRange_ShouldThrow(int index)
|
||||
{
|
||||
var groupedCollection = new ObservableGroupedCollection<string, int>();
|
||||
groupedCollection.AddGroup("A", 1, 2, 3);
|
||||
@ -369,7 +369,7 @@ public void SetItem_WhenIndexOutOfRange_ShoudThrow(int index)
|
||||
[DataRow(0, new[] { 23, 2, 3 })]
|
||||
[DataRow(1, new[] { 1, 23, 3 })]
|
||||
[DataRow(2, new[] { 1, 2, 23 })]
|
||||
public void SetItem_WithValidIndex_WithSeveralGroups_ShoudReplaceItemInFirstGroup(int index, int[] expecteGroupValues)
|
||||
public void SetItem_WithValidIndex_WithSeveralGroups_ShouldReplaceItemInFirstGroup(int index, int[] expectedGroupValues)
|
||||
{
|
||||
var groupedCollection = new ObservableGroupedCollection<string, int>();
|
||||
groupedCollection.AddGroup("A", 4, 5);
|
||||
@ -387,7 +387,7 @@ public void SetItem_WithValidIndex_WithSeveralGroups_ShoudReplaceItemInFirstGrou
|
||||
|
||||
groupedCollection.ElementAt(1).Key.Should().Be("B");
|
||||
groupedCollection.ElementAt(1).Should().HaveCount(3);
|
||||
groupedCollection.ElementAt(1).Should().ContainInOrder(expecteGroupValues);
|
||||
groupedCollection.ElementAt(1).Should().ContainInOrder(expectedGroupValues);
|
||||
|
||||
groupedCollection.ElementAt(2).Key.Should().Be("B");
|
||||
groupedCollection.ElementAt(2).Should().HaveCount(2);
|
||||
|
@ -88,9 +88,9 @@ public void Test_Guard_HasSizeGreaterThan_ArrayEqualFail()
|
||||
[TestCategory("Guard")]
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void Test_Guard_HasSizeGreaterThan_ArraSmallerFail()
|
||||
public void Test_Guard_HasSizeGreaterThan_ArraySmallerFail()
|
||||
{
|
||||
Guard.HasSizeGreaterThan(new int[1], 4, nameof(Test_Guard_HasSizeGreaterThan_ArraSmallerFail));
|
||||
Guard.HasSizeGreaterThan(new int[1], 4, nameof(Test_Guard_HasSizeGreaterThan_ArraySmallerFail));
|
||||
}
|
||||
|
||||
[TestCategory("Guard")]
|
||||
|
@ -37,6 +37,25 @@ public void Test_TaskExtensions_ResultOrDefault()
|
||||
Assert.AreEqual(42, ((Task)tcs.Task).GetResultOrDefault());
|
||||
}
|
||||
|
||||
[TestCategory("TaskExtensions")]
|
||||
[TestMethod]
|
||||
public async Task Test_TaskExtensions_ResultOrDefault_FromAsyncTaskMethodBuilder()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
Task<string> taskFromBuilder = GetTaskFromAsyncMethodBuilder("Test", tcs);
|
||||
|
||||
Assert.IsNull(((Task)taskFromBuilder).GetResultOrDefault());
|
||||
Assert.IsNull(taskFromBuilder.GetResultOrDefault());
|
||||
|
||||
tcs.SetResult(null);
|
||||
|
||||
await taskFromBuilder;
|
||||
|
||||
Assert.AreEqual(((Task)taskFromBuilder).GetResultOrDefault(), "Test");
|
||||
Assert.AreEqual(taskFromBuilder.GetResultOrDefault(), "Test");
|
||||
}
|
||||
|
||||
[TestCategory("TaskExtensions")]
|
||||
[TestMethod]
|
||||
public void Test_TaskExtensions_ResultOrDefault_OfT_Int32()
|
||||
@ -86,5 +105,16 @@ public void Test_TaskExtensions_ResultOrDefault_OfT_String()
|
||||
|
||||
Assert.AreEqual("Hello world", tcs.Task.GetResultOrDefault());
|
||||
}
|
||||
|
||||
// Creates a Task<T> of a given type which is actually an instance of
|
||||
// System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>.AsyncStateMachineBox<TStateMachine>.
|
||||
// See https://source.dot.net/#System.Private.CoreLib/AsyncTaskMethodBuilderT.cs,f8f35fd356112b30.
|
||||
// This is needed to verify that the extension also works when the input Task<T> is of a derived type.
|
||||
private static async Task<T> GetTaskFromAsyncMethodBuilder<T>(T result, TaskCompletionSource<object> tcs)
|
||||
{
|
||||
await tcs.Task;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user