1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2025-04-13 20:15:45 +02:00

Merge branch 'master' into optimization/movsxd-removal

This commit is contained in:
Sergio Pedri 2020-08-12 13:39:40 +02:00 committed by GitHub
commit ab962f6af6
12 changed files with 199 additions and 76 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")]

View File

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

View File

@ -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")]

View File

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