Skip to content

Commit 9fd4038

Browse files
Merge branch 'master' into feature/string-pool
2 parents e60d5b7 + 011a4e7 commit 9fd4038

File tree

5 files changed

+35
-38
lines changed

5 files changed

+35
-38
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
227227
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
228228
/// <param name="oldValue">The current property value.</param>
229229
/// <param name="newValue">The property's value after the change occurred.</param>
230-
/// <param name="model">The model </param>
230+
/// <param name="model">The model containing the property being updated.</param>
231231
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
232232
/// <param name="propertyName">(optional) The name of the property that changed.</param>
233233
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
@@ -236,6 +236,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
236236
/// raised if the current and new value for the target property are the same.
237237
/// </remarks>
238238
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
239+
where TModel : class
239240
{
240241
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
241242
{
@@ -263,11 +264,12 @@ protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Acti
263264
/// <param name="oldValue">The current property value.</param>
264265
/// <param name="newValue">The property's value after the change occurred.</param>
265266
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
266-
/// <param name="model">The model </param>
267+
/// <param name="model">The model containing the property being updated.</param>
267268
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
268269
/// <param name="propertyName">(optional) The name of the property that changed.</param>
269270
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
270271
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
272+
where TModel : class
271273
{
272274
if (comparer.Equals(oldValue, newValue))
273275
{

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
257257
/// <param name="propertyName">(optional) The name of the property that changed.</param>
258258
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
259259
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
260+
where TModel : class
260261
{
261262
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
262263

@@ -288,6 +289,7 @@ protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Acti
288289
/// <param name="propertyName">(optional) The name of the property that changed.</param>
289290
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
290291
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
292+
where TModel : class
291293
{
292294
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
293295

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> compa
170170
/// <param name="propertyName">(optional) The name of the property that changed.</param>
171171
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
172172
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
173+
where TModel : class
173174
{
174175
if (validate)
175176
{
@@ -199,6 +200,7 @@ protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Acti
199200
/// <param name="propertyName">(optional) The name of the property that changed.</param>
200201
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
201202
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
203+
where TModel : class
202204
{
203205
if (validate)
204206
{

Microsoft.Toolkit.Mvvm/Messaging/StrongReferenceMessenger.cs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,12 @@ public void Unregister<TMessage, TToken>(object recipient, TToken token)
325325
}
326326

327327
/// <inheritdoc/>
328-
public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
328+
public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
329329
where TMessage : class
330330
where TToken : IEquatable<TToken>
331331
{
332-
object[] handlers;
333-
object[] recipients;
334-
ref object handlersRef = ref Unsafe.AsRef<object>(null);
335-
ref object recipientsRef = ref Unsafe.AsRef<object>(null);
332+
object[] rentedArray;
333+
Span<object> pairs;
336334
int i = 0;
337335

338336
lock (this.recipientsMap)
@@ -358,10 +356,9 @@ public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
358356
return message;
359357
}
360358

361-
handlers = ArrayPool<object>.Shared.Rent(totalHandlersCount);
362-
recipients = ArrayPool<object>.Shared.Rent(totalHandlersCount);
363-
handlersRef = ref handlers[0];
364-
recipientsRef = ref recipients[0];
359+
// Rent the array and also assign it to a span, which will be used to access values.
360+
// We're doing this to avoid the array covariance checks slowdown in the loops below.
361+
pairs = rentedArray = ArrayPool<object>.Shared.Rent(2 * totalHandlersCount);
365362

366363
// Copy the handlers to the local collection.
367364
// The array is oversized at this point, since it also includes
@@ -379,10 +376,14 @@ public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
379376
// Pick the target handler, if the token is a match for the recipient
380377
if (mappingEnumerator.Value.TryGetValue(token, out object? handler))
381378
{
382-
// We can manually offset here to skip the bounds checks in this inner loop when
383-
// indexing the array (the size is already verified and guaranteed to be enough).
384-
Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)i) = handler!;
385-
Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)i++) = recipient;
379+
// This span access should always guaranteed to be valid due to the size of the
380+
// array being set according to the current total number of registered handlers,
381+
// which will always be greater or equal than the ones matching the previous test.
382+
// We're still using a checked span accesses here though to make sure an out of
383+
// bounds write can never happen even if an error was present in the logic above.
384+
pairs[2 * i] = handler!;
385+
pairs[(2 * i) + 1] = recipient;
386+
i++;
386387
}
387388
}
388389
}
@@ -392,27 +393,20 @@ public unsafe TMessage Send<TMessage, TToken>(TMessage message, TToken token)
392393
// Invoke all the necessary handlers on the local copy of entries
393394
for (int j = 0; j < i; j++)
394395
{
395-
// We're doing an unsafe cast to skip the type checks again.
396-
// See the comments in the UnregisterAll method for more info.
397-
object handler = Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)j);
398-
object recipient = Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)j);
399-
400396
// Here we perform an unsafe cast to enable covariance for delegate types.
401397
// We know that the input recipient will always respect the type constraints
402398
// of each original input delegate, and doing so allows us to still invoke
403399
// them all from here without worrying about specific generic type arguments.
404-
Unsafe.As<MessageHandler<object, TMessage>>(handler)(recipient, message);
400+
Unsafe.As<MessageHandler<object, TMessage>>(pairs[2 * j])(pairs[(2 * j) + 1], message);
405401
}
406402
}
407403
finally
408404
{
409405
// As before, we also need to clear it first to avoid having potentially long
410406
// lasting memory leaks due to leftover references being stored in the pool.
411-
handlers.AsSpan(0, i).Clear();
412-
recipients.AsSpan(0, i).Clear();
407+
Array.Clear(rentedArray, 0, 2 * i);
413408

414-
ArrayPool<object>.Shared.Return(handlers);
415-
ArrayPool<object>.Shared.Return(recipients);
409+
ArrayPool<object>.Shared.Return(rentedArray);
416410
}
417411

418412
return message;

Microsoft.Toolkit.Mvvm/Messaging/WeakReferenceMessenger.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
169169
where TMessage : class
170170
where TToken : IEquatable<TToken>
171171
{
172-
ArrayPoolBufferWriter<object> recipients;
173-
ArrayPoolBufferWriter<object> handlers;
172+
ArrayPoolBufferWriter<object> bufferWriter;
173+
int i = 0;
174174

175175
lock (this.recipientsMap)
176176
{
@@ -182,8 +182,7 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
182182
return message;
183183
}
184184

185-
recipients = ArrayPoolBufferWriter<object>.Create();
186-
handlers = ArrayPoolBufferWriter<object>.Create();
185+
bufferWriter = ArrayPoolBufferWriter<object>.Create();
187186

188187
// We need a local, temporary copy of all the pending recipients and handlers to
189188
// invoke, to avoid issues with handlers unregistering from messages while we're
@@ -197,32 +196,30 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
197196

198197
if (map.TryGetValue(token, out object? handler))
199198
{
200-
recipients.Add(pair.Key);
201-
handlers.Add(handler!);
199+
bufferWriter.Add(handler!);
200+
bufferWriter.Add(pair.Key);
201+
i++;
202202
}
203203
}
204204
}
205205

206206
try
207207
{
208-
ReadOnlySpan<object>
209-
recipientsSpan = recipients.Span,
210-
handlersSpan = handlers.Span;
208+
ReadOnlySpan<object> pairs = bufferWriter.Span;
211209

212-
for (int i = 0; i < recipientsSpan.Length; i++)
210+
for (int j = 0; j < i; j++)
213211
{
214212
// Just like in the other messenger, here we need an unsafe cast to be able to
215213
// invoke a generic delegate with a contravariant input argument, with a less
216214
// derived reference, without reflection. This is guaranteed to work by how the
217215
// messenger tracks registered recipients and their associated handlers, so the
218216
// type conversion will always be valid (the recipients are the rigth instances).
219-
Unsafe.As<MessageHandler<object, TMessage>>(handlersSpan[i])(recipientsSpan[i], message);
217+
Unsafe.As<MessageHandler<object, TMessage>>(pairs[2 * j])(pairs[(2 * j) + 1], message);
220218
}
221219
}
222220
finally
223221
{
224-
recipients.Dispose();
225-
handlers.Dispose();
222+
bufferWriter.Dispose();
226223
}
227224

228225
return message;

0 commit comments

Comments
 (0)