Skip to content

Commit 8528591

Browse files
committed
Merge branch 'master' into muxtestinfra
2 parents 25b6ca7 + 49a88c7 commit 8528591

20 files changed

+1450
-435
lines changed

Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ public abstract class ObservableRecipient : ObservableObject
2525
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
2626
/// </summary>
2727
/// <remarks>
28-
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
28+
/// This constructor will produce an instance that will use the <see cref="WeakReferenceMessenger.Default"/> instance
2929
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
3030
/// </remarks>
3131
protected ObservableRecipient()
32-
: this(Messaging.Messenger.Default)
32+
: this(WeakReferenceMessenger.Default)
3333
{
3434
}
3535

@@ -78,7 +78,7 @@ public bool IsActive
7878
/// <remarks>
7979
/// The base implementation registers all messages for this recipients that have been declared
8080
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
81-
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
81+
/// For more details on how this works, see the <see cref="IMessengerExtensions.RegisterAll"/> method.
8282
/// If you need more fine tuned control, want to register messages individually or just prefer
8383
/// the lambda-style syntax for message registration, override this method and register manually.
8484
/// </remarks>

Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,70 @@
77

88
namespace Microsoft.Toolkit.Mvvm.Messaging
99
{
10+
/// <summary>
11+
/// A <see langword="delegate"/> used to represent actions to invoke when a message is received.
12+
/// The recipient is given as an input argument to allow message registrations to avoid creating
13+
/// closures: if an instance method on a recipient needs to be invoked it is possible to just
14+
/// cast the recipient to the right type and then access the local method from that instance.
15+
/// </summary>
16+
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
17+
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
18+
/// <param name="recipient">The recipient that is receiving the message.</param>
19+
/// <param name="message">The message being received.</param>
20+
public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recipient, TMessage message)
21+
where TRecipient : class
22+
where TMessage : class;
23+
1024
/// <summary>
1125
/// An interface for a type providing the ability to exchange messages between different objects.
26+
/// This can be useful to decouple different modules of an application without having to keep strong
27+
/// references to types being referenced. It is also possible to send messages to specific channels, uniquely
28+
/// identified by a token, and to have different messengers in different sections of an applications.
29+
/// In order to use the <see cref="IMessenger"/> functionalities, first define a message type, like so:
30+
/// <code>
31+
/// public sealed class LoginCompletedMessage { }
32+
/// </code>
33+
/// Then, register your a recipient for this message:
34+
/// <code>
35+
/// Messenger.Default.Register&lt;MyRecipientType, LoginCompletedMessage&gt;(this, (r, m) =>
36+
/// {
37+
/// // Handle the message here...
38+
/// });
39+
/// </code>
40+
/// The message handler here is a lambda expression taking two parameters: the recipient and the message.
41+
/// This is done to avoid the allocations for the closures that would've been generated if the expression
42+
/// had captured the current instance. The recipient type parameter is used so that the recipient can be
43+
/// directly accessed within the handler without the need to manually perform type casts. This allows the
44+
/// code to be less verbose and more reliable, as all the checks are done just at build time. If the handler
45+
/// is defined within the same type as the recipient, it is also possible to directly access private members.
46+
/// This allows the message handler to be a static method, which enables the C# compiler to perform a number
47+
/// of additional memory optimizations (such as caching the delegate, avoiding unnecessary memory allocations).
48+
/// Finally, send a message when needed, like so:
49+
/// <code>
50+
/// Messenger.Default.Send&lt;LoginCompletedMessage&gt;();
51+
/// </code>
52+
/// Additionally, the method group syntax can also be used to specify the message handler
53+
/// to invoke when receiving a message, if a method with the right signature is available
54+
/// in the current scope. This is helpful to keep the registration and handling logic separate.
55+
/// Following up from the previous example, consider a class having this method:
56+
/// <code>
57+
/// private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
58+
/// {
59+
/// // Handle the message there
60+
/// }
61+
/// </code>
62+
/// The registration can then be performed in a single line like so:
63+
/// <code>
64+
/// Messenger.Default.Register(this, Receive);
65+
/// </code>
66+
/// The C# compiler will automatically convert that expression to a <see cref="MessageHandler{TRecipient,TMessage}"/> instance
67+
/// compatible with <see cref="IMessengerExtensions.Register{TRecipient,TMessage}(IMessenger,TRecipient,MessageHandler{TRecipient,TMessage})"/>.
68+
/// This will also work if multiple overloads of that method are available, each handling a different
69+
/// message type: the C# compiler will automatically pick the right one for the current message type.
70+
/// It is also possible to register message handlers explicitly using the <see cref="IRecipient{TMessage}"/> interface.
71+
/// To do so, the recipient just needs to implement the interface and then call the
72+
/// <see cref="IMessengerExtensions.RegisterAll(IMessenger,object)"/> extension, which will automatically register
73+
/// all the handlers that are declared by the recipient type. Registration for individual handlers is supported as well.
1274
/// </summary>
1375
public interface IMessenger
1476
{
@@ -28,13 +90,15 @@ bool IsRegistered<TMessage, TToken>(object recipient, TToken token)
2890
/// <summary>
2991
/// Registers a recipient for a given type of message.
3092
/// </summary>
93+
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
3194
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
3295
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
3396
/// <param name="recipient">The recipient that will receive the messages.</param>
3497
/// <param name="token">A token used to determine the receiving channel to use.</param>
35-
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
98+
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
3699
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
37-
void Register<TMessage, TToken>(object recipient, TToken token, Action<TMessage> action)
100+
void Register<TRecipient, TMessage, TToken>(TRecipient recipient, TToken token, MessageHandler<TRecipient, TMessage> handler)
101+
where TRecipient : class
38102
where TMessage : class
39103
where TToken : IEquatable<TToken>;
40104

@@ -83,6 +147,14 @@ TMessage Send<TMessage, TToken>(TMessage message, TToken token)
83147
where TMessage : class
84148
where TToken : IEquatable<TToken>;
85149

150+
/// <summary>
151+
/// Performs a cleanup on the current messenger.
152+
/// Invoking this method does not unregister any of the currently registered
153+
/// recipient, and it can be used to perform cleanup operations such as
154+
/// trimming the internal data structures of a messenger implementation.
155+
/// </summary>
156+
void Cleanup();
157+
86158
/// <summary>
87159
/// Resets the <see cref="IMessenger"/> instance and unregisters all the existing recipients.
88160
/// </summary>

Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88
using System.Linq.Expressions;
99
using System.Reflection;
1010
using System.Runtime.CompilerServices;
11+
using Microsoft.Toolkit.Mvvm.Messaging.Internals;
1112

1213
namespace Microsoft.Toolkit.Mvvm.Messaging
1314
{
1415
/// <summary>
1516
/// Extensions for the <see cref="IMessenger"/> type.
1617
/// </summary>
17-
public static partial class MessengerExtensions
18+
public static class IMessengerExtensions
1819
{
1920
/// <summary>
2021
/// A class that acts as a container to load the <see cref="MethodInfo"/> instance linked to
2122
/// the <see cref="Register{TMessage,TToken}(IMessenger,IRecipient{TMessage},TToken)"/> method.
2223
/// 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+
/// the <see cref="IMessengerExtensions"/> type is referenced, even if that is done just to use methods
2425
/// that do not actually require this <see cref="MethodInfo"/> instance to be available.
2526
/// We're effectively using this type to leverage the lazy loading of static constructors done by the runtime.
2627
/// </summary>
@@ -32,7 +33,7 @@ private static class MethodInfos
3233
static MethodInfos()
3334
{
3435
RegisterIRecipient = (
35-
from methodInfo in typeof(MessengerExtensions).GetMethods()
36+
from methodInfo in typeof(IMessengerExtensions).GetMethods()
3637
where methodInfo.Name == nameof(Register) &&
3738
methodInfo.IsGenericMethod &&
3839
methodInfo.GetGenericArguments().Length == 2
@@ -174,7 +175,7 @@ static Action<IMessenger, object, TToken> GetRegistrationAction(Type type, Metho
174175
public static void Register<TMessage>(this IMessenger messenger, IRecipient<TMessage> recipient)
175176
where TMessage : class
176177
{
177-
messenger.Register<TMessage, Unit>(recipient, default, recipient.Receive);
178+
messenger.Register<IRecipient<TMessage>, TMessage, Unit>(recipient, default, (r, m) => r.Receive(m));
178179
}
179180

180181
/// <summary>
@@ -191,7 +192,7 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
191192
where TMessage : class
192193
where TToken : IEquatable<TToken>
193194
{
194-
messenger.Register<TMessage, TToken>(recipient, token, recipient.Receive);
195+
messenger.Register<IRecipient<TMessage>, TMessage, TToken>(recipient, token, (r, m) => r.Receive(m));
195196
}
196197

197198
/// <summary>
@@ -200,13 +201,47 @@ public static void Register<TMessage, TToken>(this IMessenger messenger, IRecipi
200201
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
201202
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
202203
/// <param name="recipient">The recipient that will receive the messages.</param>
203-
/// <param name="action">The <see cref="Action{T}"/> to invoke when a message is received.</param>
204+
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
204205
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
205206
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
206-
public static void Register<TMessage>(this IMessenger messenger, object recipient, Action<TMessage> action)
207+
public static void Register<TMessage>(this IMessenger messenger, object recipient, MessageHandler<object, TMessage> handler)
207208
where TMessage : class
208209
{
209-
messenger.Register(recipient, default(Unit), action);
210+
messenger.Register(recipient, default(Unit), handler);
211+
}
212+
213+
/// <summary>
214+
/// Registers a recipient for a given type of message.
215+
/// </summary>
216+
/// <typeparam name="TRecipient">The type of recipient for the message.</typeparam>
217+
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
218+
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
219+
/// <param name="recipient">The recipient that will receive the messages.</param>
220+
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
221+
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
222+
/// <remarks>This method will use the default channel to perform the requested registration.</remarks>
223+
public static void Register<TRecipient, TMessage>(this IMessenger messenger, TRecipient recipient, MessageHandler<TRecipient, TMessage> handler)
224+
where TRecipient : class
225+
where TMessage : class
226+
{
227+
messenger.Register(recipient, default(Unit), handler);
228+
}
229+
230+
/// <summary>
231+
/// Registers a recipient for a given type of message.
232+
/// </summary>
233+
/// <typeparam name="TMessage">The type of message to receive.</typeparam>
234+
/// <typeparam name="TToken">The type of token to use to pick the messages to receive.</typeparam>
235+
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to register the recipient.</param>
236+
/// <param name="recipient">The recipient that will receive the messages.</param>
237+
/// <param name="token">A token used to determine the receiving channel to use.</param>
238+
/// <param name="handler">The <see cref="MessageHandler{TRecipient,TMessage}"/> to invoke when a message is received.</param>
239+
/// <exception cref="InvalidOperationException">Thrown when trying to register the same message twice.</exception>
240+
public static void Register<TMessage, TToken>(this IMessenger messenger, object recipient, TToken token, MessageHandler<object, TMessage> handler)
241+
where TMessage : class
242+
where TToken : IEquatable<TToken>
243+
{
244+
messenger.Register(recipient, token, handler);
210245
}
211246

212247
/// <summary>

Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/Internals/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,11 @@ public void Clear()
130130
this.entries = InitialEntries;
131131
}
132132

133-
/// <inheritdoc cref="Dictionary{TKey,TValue}.ContainsKey"/>
133+
/// <summary>
134+
/// Checks whether or not the dictionary contains a pair with a specified key.
135+
/// </summary>
136+
/// <param name="key">The key to look for.</param>
137+
/// <returns>Whether or not the key was present in the dictionary.</returns>
134138
public bool ContainsKey(TKey key)
135139
{
136140
Entry[] entries = this.entries;
@@ -176,7 +180,18 @@ public bool TryGetValue(TKey key, out TValue? value)
176180
}
177181

178182
/// <inheritdoc/>
179-
public bool TryRemove(TKey key, out object? result)
183+
public bool TryRemove(TKey key)
184+
{
185+
return TryRemove(key, out _);
186+
}
187+
188+
/// <summary>
189+
/// Tries to remove a value with a specified key, if present.
190+
/// </summary>
191+
/// <param name="key">The key of the value to remove.</param>
192+
/// <param name="result">The removed value, if it was present.</param>
193+
/// <returns>Whether or not the key was present.</returns>
194+
public bool TryRemove(TKey key, out TValue? result)
180195
{
181196
Entry[] entries = this.entries;
182197
int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1);
@@ -218,13 +233,6 @@ public bool TryRemove(TKey key, out object? result)
218233
return false;
219234
}
220235

221-
/// <inheritdoc/>
222-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
223-
public bool Remove(TKey key)
224-
{
225-
return TryRemove(key, out _);
226-
}
227-
228236
/// <summary>
229237
/// Gets the value for the specified key, or, if the key is not present,
230238
/// adds an entry and returns the value by ref. This makes it possible to

Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs renamed to Microsoft.Toolkit.Mvvm/Messaging/Internals/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,10 @@ internal interface IDictionarySlim<in TKey> : IDictionarySlim
1414
where TKey : IEquatable<TKey>
1515
{
1616
/// <summary>
17-
/// Tries to remove a value with a specified key.
17+
/// Tries to remove a value with a specified key, if present.
1818
/// </summary>
1919
/// <param name="key">The key of the value to remove.</param>
20-
/// <param name="result">The removed value, if it was present.</param>
21-
/// <returns>.Whether or not the key was present.</returns>
22-
bool TryRemove(TKey key, out object? result);
23-
24-
/// <summary>
25-
/// Removes an item from the dictionary with the specified key, if present.
26-
/// </summary>
27-
/// <param name="key">The key of the item to remove.</param>
28-
/// <returns>Whether or not an item was removed.</returns>
29-
bool Remove(TKey key);
20+
/// <returns>Whether or not the key was present.</returns>
21+
bool TryRemove(TKey key);
3022
}
3123
}

0 commit comments

Comments
 (0)