Skip to content

Commit 84918ef

Browse files
authored
Merge branch 'master' into improvement/high-performance-tweaks
2 parents cf0f8aa + 5bf4265 commit 84918ef

File tree

77 files changed

+497
-376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+497
-376
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,26 @@ protected virtual void OnPropertyChanging([CallerMemberName] string? propertyNam
6666
/// </remarks>
6767
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
6868
{
69-
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, propertyName);
69+
// We duplicate the code here instead of calling the overload because we can't
70+
// guarantee that the invoked SetProperty<T> will be inlined, and we need the JIT
71+
// to be able to see the full EqualityComparer<T>.Default.Equals call, so that
72+
// it'll use the intrinsics version of it and just replace the whole invocation
73+
// with a direct comparison when possible (eg. for primitive numeric types).
74+
// This is the fastest SetProperty<T> overload so we particularly care about
75+
// the codegen quality here, and the code is small and simple enough so that
76+
// duplicating it still doesn't make the whole class harder to maintain.
77+
if (EqualityComparer<T>.Default.Equals(field, newValue))
78+
{
79+
return false;
80+
}
81+
82+
OnPropertyChanging(propertyName);
83+
84+
field = newValue;
85+
86+
OnPropertyChanged(propertyName);
87+
88+
return true;
7089
}
7190

7291
/// <summary>
@@ -203,7 +222,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
203222
/// </remarks>
204223
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, [CallerMemberName] string? propertyName = null)
205224
{
206-
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, propertyName);
225+
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, out _, propertyName);
207226
}
208227

209228
/// <summary>
@@ -220,6 +239,21 @@ protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue
220239
/// <param name="propertyName">(optional) The name of the property that changed.</param>
221240
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
222241
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string? propertyName = null)
242+
{
243+
return SetProperty(propertyExpression, newValue, comparer, out _, propertyName);
244+
}
245+
246+
/// <summary>
247+
/// Implements the shared logic for <see cref="SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>
248+
/// </summary>
249+
/// <typeparam name="T">The type of property to set.</typeparam>
250+
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
251+
/// <param name="newValue">The property's value after the change occurred.</param>
252+
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
253+
/// <param name="oldValue">The resulting initial value for the target property.</param>
254+
/// <param name="propertyName">(optional) The name of the property that changed.</param>
255+
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
256+
private protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, out T oldValue, [CallerMemberName] string? propertyName = null)
223257
{
224258
PropertyInfo? parentPropertyInfo;
225259
FieldInfo? parentFieldInfo = null;
@@ -236,13 +270,15 @@ parentExpression.Expression is ConstantExpression instanceExpression &&
236270
ThrowArgumentExceptionForInvalidPropertyExpression();
237271

238272
// This is never executed, as the method above always throws
273+
oldValue = default!;
274+
239275
return false;
240276
}
241277

242278
object parent = parentPropertyInfo is null
243279
? parentFieldInfo!.GetValue(instance)
244280
: parentPropertyInfo.GetValue(instance);
245-
T oldValue = (T)targetPropertyInfo.GetValue(parent);
281+
oldValue = (T)targetPropertyInfo.GetValue(parent);
246282

247283
if (comparer.Equals(oldValue, newValue))
248284
{

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
using System;
1111
using System.Collections.Generic;
12+
using System.Linq.Expressions;
1213
using System.Runtime.CompilerServices;
1314
using Microsoft.Toolkit.Mvvm.Messaging;
1415
using Microsoft.Toolkit.Mvvm.Messaging.Messages;
@@ -140,7 +141,18 @@ protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName
140141
/// </remarks>
141142
protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
142143
{
143-
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
144+
T oldValue = field;
145+
146+
// We duplicate the code as in the base class here to leverage
147+
// the intrinsics support for EqualityComparer<T>.Default.Equals.
148+
bool propertyChanged = SetProperty(ref field, newValue, propertyName);
149+
150+
if (propertyChanged && broadcast)
151+
{
152+
Broadcast(oldValue, newValue, propertyName);
153+
}
154+
155+
return propertyChanged;
144156
}
145157

146158
/// <summary>
@@ -158,21 +170,16 @@ protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMe
158170
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
159171
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
160172
{
161-
if (!broadcast)
162-
{
163-
return SetProperty(ref field, newValue, comparer, propertyName);
164-
}
165-
166173
T oldValue = field;
167174

168-
if (SetProperty(ref field, newValue, comparer, propertyName))
175+
bool propertyChanged = SetProperty(ref field, newValue, comparer, propertyName);
176+
177+
if (propertyChanged && broadcast)
169178
{
170179
Broadcast(oldValue, newValue, propertyName);
171-
172-
return true;
173180
}
174181

175-
return false;
182+
return propertyChanged;
176183
}
177184

178185
/// <summary>
@@ -216,19 +223,61 @@ protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool b
216223
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
217224
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
218225
{
219-
if (!broadcast)
226+
bool propertyChanged = SetProperty(oldValue, newValue, comparer, callback, propertyName);
227+
228+
if (propertyChanged && broadcast)
220229
{
221-
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
230+
Broadcast(oldValue, newValue, propertyName);
222231
}
223232

224-
if (SetProperty(oldValue, newValue, comparer, callback, propertyName))
233+
return propertyChanged;
234+
}
235+
236+
/// <summary>
237+
/// Compares the current and new values for a given nested property. If the value has changed,
238+
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
239+
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
240+
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>, with the difference being that this
241+
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
242+
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>.
243+
/// </summary>
244+
/// <typeparam name="T">The type of property to set.</typeparam>
245+
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
246+
/// <param name="newValue">The property's value after the change occurred.</param>
247+
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
248+
/// <param name="propertyName">(optional) The name of the property that changed.</param>
249+
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
250+
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
251+
{
252+
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
253+
}
254+
255+
/// <summary>
256+
/// Compares the current and new values for a given nested property. If the value has changed,
257+
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
258+
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
259+
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>,
260+
/// with the difference being that this method is used to relay properties from a wrapped model in the
261+
/// current instance. For more info, see the docs for
262+
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>.
263+
/// </summary>
264+
/// <typeparam name="T">The type of property to set.</typeparam>
265+
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
266+
/// <param name="newValue">The property's value after the change occurred.</param>
267+
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
268+
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
269+
/// <param name="propertyName">(optional) The name of the property that changed.</param>
270+
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
271+
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
272+
{
273+
bool propertyChanged = SetProperty(propertyExpression, newValue, comparer, out T oldValue, propertyName);
274+
275+
if (propertyChanged && broadcast)
225276
{
226277
Broadcast(oldValue, newValue, propertyName);
227-
228-
return true;
229278
}
230279

231-
return false;
280+
return propertyChanged;
232281
}
233282
}
234283
}

Microsoft.Toolkit.Mvvm/DependencyInjection/Ioc.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public sealed class Ioc : IServiceProvider
5151
/// <summary>
5252
/// The <see cref="ServiceProvider"/> instance to use, if initialized.
5353
/// </summary>
54-
private ServiceProvider? serviceProvider;
54+
private volatile ServiceProvider? serviceProvider;
5555

5656
/// <inheritdoc/>
5757
object? IServiceProvider.GetService(Type serviceType)

Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,9 +638,14 @@ public Type2(Type tMessage, Type tToken)
638638
[MethodImpl(MethodImplOptions.AggressiveInlining)]
639639
public bool Equals(Type2 other)
640640
{
641+
// We can't just use reference equality, as that's technically not guaranteed
642+
// to work and might fail in very rare cases (eg. with type forwarding between
643+
// different assemblies). Instead, we can use the == operator to compare for
644+
// equality, which still avoids the callvirt overhead of calling Type.Equals,
645+
// and is also implemented as a JIT intrinsic on runtimes such as .NET Core.
641646
return
642-
ReferenceEquals(this.tMessage, other.tMessage) &&
643-
ReferenceEquals(this.tToken, other.tToken);
647+
this.tMessage == other.tMessage &&
648+
this.tToken == other.tToken;
644649
}
645650

646651
/// <inheritdoc/>

Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,37 @@ namespace Microsoft.Toolkit.Mvvm.Messaging
1717
public static partial class MessengerExtensions
1818
{
1919
/// <summary>
20-
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
20+
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
21+
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
22+
/// This class is needed to avoid forcing the initialization code in the static constructor to run as soon as
23+
/// the <see cref="MessengerExtensions"/> type is referenced, even if that is done just to use methods
24+
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
25+
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
2126
/// </summary>
22-
private static readonly MethodInfo RegisterIRecipientMethodInfo;
27+
private static class MethodInfos
28+
{
29+
/// <summary>
30+
/// Initializes static members of the <see cref="MethodInfos"/> class.
31+
/// </summary>
32+
static MethodInfos()
33+
{
34+
RegisterIRecipient = (
35+
from methodInfo in typeof(MessengerExtensions).GetMethods()
36+
where methodInfo.Name == nameof(Register) &&
37+
methodInfo.IsGenericMethod &&
38+
methodInfo.GetGenericArguments().Length == 2
39+
let parameters = methodInfo.GetParameters()
40+
where parameters.Length == 3 &&
41+
parameters[1].ParameterType.IsGenericType &&
42+
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
43+
select methodInfo).First();
44+
}
45+
46+
/// <summary>
47+
/// The <see cref="MethodInfo"/> instance associated with <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/>.
48+
/// </summary>
49+
public static readonly MethodInfo RegisterIRecipient;
50+
}
2351

2452
/// <summary>
2553
/// 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
3967
= new ConditionalWeakTable<Type, Action<IMessenger, object, TToken>[]>();
4068
}
4169

42-
/// <summary>
43-
/// Initializes static members of the <see cref="MessengerExtensions"/> class.
44-
/// </summary>
45-
static MessengerExtensions()
46-
{
47-
RegisterIRecipientMethodInfo = (
48-
from methodInfo in typeof(MessengerExtensions).GetMethods()
49-
where methodInfo.Name == nameof(Register) &&
50-
methodInfo.IsGenericMethod &&
51-
methodInfo.GetGenericArguments().Length == 2
52-
let parameters = methodInfo.GetParameters()
53-
where parameters.Length == 3 &&
54-
parameters[1].ParameterType.IsGenericType &&
55-
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(IRecipient<>)
56-
select methodInfo).First();
57-
}
58-
5970
/// <summary>
6071
/// Checks whether or not a given recipient has already been registered for a message.
6172
/// </summary>
@@ -110,7 +121,7 @@ from interfaceType in type.GetInterfaces()
110121
where interfaceType.IsGenericType &&
111122
interfaceType.GetGenericTypeDefinition() == typeof(IRecipient<>)
112123
let messageType = interfaceType.GenericTypeArguments[0]
113-
let registrationMethod = RegisterIRecipientMethodInfo.MakeGenericMethod(messageType, typeof(TToken))
124+
let registrationMethod = MethodInfos.RegisterIRecipient.MakeGenericMethod(messageType, typeof(TToken))
114125
let registrationAction = GetRegistrationAction(type, registrationMethod)
115126
select registrationAction).ToArray();
116127
}

Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
55
<LangVersion>8.0</LangVersion>
66
<Nullable>enable</Nullable>
7-
<Title>Windows Community Toolkit Mvvm .NET Standard</Title>
7+
<Title>Windows Community Toolkit MVVM Toolkit</Title>
88
<Description>
9-
This package includes Mvvm .NET Standard code only helpers such as:
9+
This package includes a .NET Standard MVVM library with helpers such as:
1010
- ObservableObject: a base class for objects implementing the INotifyPropertyChanged interface.
1111
- ObservableRecipient: a base class for observable objects with support for the IMessenger service.
1212
- RelayCommand: a simple delegate command implementing the ICommand interface.
1313
- Messenger: a messaging system to exchange messages through different loosely-coupled objects.
1414
- Ioc: a helper class to configure dependency injection service containers.
1515
</Description>
16-
<PackageTags>UWP Toolkit Windows Mvvm observable Ioc dependency injection services extensions helpers</PackageTags>
16+
<PackageTags>UWP Toolkit Windows MVVM MVVMToolkit observable Ioc dependency injection services extensions helpers</PackageTags>
1717
</PropertyGroup>
1818

1919
<!-- .NET Standard 2.0 doesn't have the Span<T> type -->

Microsoft.Toolkit.Parsers/Markdown/Inlines/EmojiInline.EmojiCodes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Toolkit.Parsers.Markdown.Inlines
1212
public partial class EmojiInline
1313
{
1414
// Codes taken from https://gist.github.com/rxaviers/7360908
15-
// Ignoring not implented symbols in Segoe UI Emoji font (e.g. :bowtie:)
15+
// Ignoring not implemented symbols in Segoe UI Emoji font (e.g. :bowtie:)
1616
private static readonly Dictionary<string, int> _emojiCodesDictionary = new Dictionary<string, int>
1717
{
1818
{ "smile", 0x1f604 },

Microsoft.Toolkit.Parsers/Markdown/Inlines/HyperlinkInline.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ internal static InlineParseResult ParseEmailAddress(string markdown, int minStar
325325
// reddit (for example: '$' and '!').
326326

327327
// Special characters as per https://en.wikipedia.org/wiki/Email_address#Local-part allowed
328-
char[] allowedchars = new char[] { '!', '#', '$', '%', '&', '\'', '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~' };
328+
char[] allowedChars = { '!', '#', '$', '%', '&', '\'', '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~' };
329329

330330
int start = tripPos;
331331
while (start > minStart)
@@ -334,7 +334,7 @@ internal static InlineParseResult ParseEmailAddress(string markdown, int minStar
334334
if ((c < 'a' || c > 'z') &&
335335
(c < 'A' || c > 'Z') &&
336336
(c < '0' || c > '9') &&
337-
!allowedchars.Contains(c))
337+
!allowedChars.Contains(c))
338338
{
339339
break;
340340
}

0 commit comments

Comments
 (0)