Skip to content

Commit 5003d67

Browse files
committed
Updated Messenger docs
1 parent 905e25d commit 5003d67

File tree

1 file changed

+50
-19
lines changed

1 file changed

+50
-19
lines changed

docs/mvvm/Messenger.md

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ dev_langs:
99

1010
# Messenger
1111

12-
The [`Messenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.Messenger) class (with the accompanying [`IMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.IMessenger) interface) can be used to exchange messages between different objects. This can be useful to decouple different modules of an application without having to keep strong references to types being referenced. It is also possible to send messages to specific channels, uniquely identified by a token, and to have different messengers in different sections of an application.
12+
The [`IMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.IMessenger) interface is a contract for types that can be used to exchange messages between different objects. This can be useful to decouple different modules of an application without having to keep strong references to types being referenced. It is also possible to send messages to specific channels, uniquely identified by a token, and to have different messengers in different sections of an application. The MVVM Toolkit provides two implementations out of the box: [`WeakReferenceMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.WeakReferenceMessenger) and [`StrongReferenceMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.StrongReferenceMessenger): the former uses weak references internally, offering automatic memory management for recipients, while the latter uses strong references and requires users to manually unsubscribe their recipients when they're no longer needed, but in exchange for that offers better performance and far less memory usage.
1313

1414
## How it works
1515

16-
The `Messenger` type is responsible for maintaining links between recipients (receivers of messages) and their registered message types, with relative message handlers. Any object can be registered as a recipient for a given message type using a message handler, which will be invoked whenever the `Messenger` instance is used to send a message of that type. It is also possible to send messages through specific communication channels (each identified by a unique token), so that multiple modules can exchange messages of the same type without causing conflicts. Messages sent without a token use the default shared channel.
16+
Types implementing `IMessenger` are responsible for maintaining links between recipients (receivers of messages) and their registered message types, with relative message handlers. Any object can be registered as a recipient for a given message type using a message handler, which will be invoked whenever the `IMessenger` instance is used to send a message of that type. It is also possible to send messages through specific communication channels (each identified by a unique token), so that multiple modules can exchange messages of the same type without causing conflicts. Messages sent without a token use the default shared channel.
1717

18-
There are two ways to perform message registration: either through the `IRecipient<TMessage>` interface, or using an `Action<TMessage>` delegate acting as message handler. The first lets you register all the handlers with a single call to the `RegisterAll` extension, which automatically registers the recipients of all the declared message handlers, while the latter is useful when you need more flexibility or when you want to use a simple lambda expression as a message handler.
18+
There are two ways to perform message registration: either through the `IRecipient<TMessage>` interface, or using a `MessageHandler<TRecipient, TMessage>` delegate acting as message handler. The first lets you register all the handlers with a single call to the `RegisterAll` extension, which automatically registers the recipients of all the declared message handlers, while the latter is useful when you need more flexibility or when you want to use a simple lambda expression as a message handler.
1919

20-
Similar to the `Ioc` class, `Messenger` exposes a `Default` property that offers a thread-safe implementation built-in into the package. It is also possible to create multiple `Messenger` instances if needed, for instance if a different one is injected with a DI service provider into a different module of the app (for instance, multiple windows running in the same process).
20+
Both `WeakReferenceMessenger` and `StrongReferenceMessenger` also expose a `Default` property that offers a thread-safe implementation built-in into the package. It is also possible to create multiple messenger instances if needed, for instance if a different one is injected with a DI service provider into a different module of the app (for instance, multiple windows running in the same process).
21+
22+
> [!NOTE]
23+
> Since the `WeakReferenceMessenger` type is simpler to use and matches the behavior of the messenger type from the `MvvmLight` library, it is the default type being used by the [`ObservableRecipient`](ObservableRecipient.md) type in the MVVM Toolkit. The `StrongReferenceType` can still be used, by passing an instance to the constructor of that class.
2124
2225
## Sending and receiving messages
2326

@@ -33,34 +36,58 @@ public class LoggedInUserChangedMessage : ValueChangedMessage<User>
3336
}
3437

3538
// Register a message in some module
36-
Messenger.Default.Register<LoggedInUserChangedMessage>(this, m =>
39+
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
3740
{
38-
// Handle the message here
41+
// Handle the message here, with r being the recipient and m being the
42+
// input messenger. Using the recipient passed as input makes it so that
43+
// the lambda expression doesn't capture "this", improving performance.
3944
});
4045

4146
// Send a message from some other module
42-
Messenger.Default.Send(new LoggedInUserChangedMessage(user));
47+
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
4348
```
4449

4550
Let's imagine this message type being used in a simple messaging application, which displays a header with the user name and profile image of the currently logged user, a panel with a list of conversations, and another panel with messages from the current conversation, if one is selected. Let's say these three sections are supported by the `HeaderViewModel`, `ConversationsListViewModel` and `ConversationViewModel` types respectively. In this scenario, the `LoggedInUserChangedMessage` message might be sent by the `HeaderViewModel` after a login operation has completed, and both those other viewmodels might register handlers for it. For instance, `ConversationsListViewModel` will load the list of conversations for the new user, and `ConversationViewModel` will just close the current conversation, if one is present.
4651

47-
The `Messenger` class takes care of delivering messages to all the registered recipients. Note that a recipient can subscribe to messages of a specific type. Note that inherited message types are not registered in the default `Messenger` implementation.
52+
The `IMessenger` instance takes care of delivering messages to all the registered recipients. Note that a recipient can subscribe to messages of a specific type. Note that inherited message types are not registered in the default `IMessenger` implementations provided by the MVVM Toolkit.
4853

4954
When a recipient is not needed anymore, you should unregister it so that it will stop receiving messages. You can unregister either by message type, by registration token, or by recipient:
5055

5156
```csharp
5257
// Unregisters the recipient from a message type
53-
Messenger.Default.Unregister<LoggedInUserChangedMessage>(this);
58+
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage>(this);
5459

5560
// Unregisters the recipient from a message type in a specified channel
56-
Messenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);
61+
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);
5762

5863
// Unregister the recipient from all messages, across all channels
59-
Messenger.Default.UnregisterAll(this);
64+
WeakReferenceMessenger.Default.UnregisterAll(this);
6065
```
6166

6267
> [!WARNING]
63-
> The `Messenger` implementation uses strong references to track the registered recipients. This is done for performance reasons, and it means that each registered recipient should manually be unregistered to avoid memory leaks. That is, as long as a recipient is registered, the `Messenger` instance in use will keep an active reference to it, which will prevent the garbage collector from being able to collect that instance. You can either handle this manually, or you can inherit from [`ObservableRecipient`](ObservableRecipient.md), which by default automatically takes care of removing all the message registrations for recipient when it is deactivated (see docs on `ObservableRecipient` for more info about this).
68+
> As mentioned before, this is not strictly necessary when using the `WeakReferenceMessenger` type, as it uses weak references to track recipients, meaning that unused recipients will still be eligible for garbage collection even though they still have active message handlers. It is still good practice to unsubscribe them though, to improve performances. On the other hand, the `StrongReferenceMessenger` implementation uses strong references to track the registered recipients. This is done for performance reasons, and it means that each registered recipient should manually be unregistered to avoid memory leaks. That is, as long as a recipient is registered, the `StrongReferenceMessenger` instance in use will keep an active reference to it, which will prevent the garbage collector from being able to collect that instance. You can either handle this manually, or you can inherit from `ObservableRecipient`, which by default automatically takes care of removing all the message registrations for recipient when it is deactivated (see docs on `ObservableRecipient` for more info about this).
69+
70+
It is also possible to use the `IRecipient<TMessage>` interface to register message handlers. In this case, each recipient will need to implement the interface for a given message type, and provide a `Receive(TMessage)` method that will be invoke when receiving messages, like so:
71+
72+
```csharp
73+
// Create a message
74+
public class MyRecipient : IRecipient<LoggedInUserChangedMessage>
75+
{
76+
public void Receive(LoggedInUserChangedMessage message)
77+
{
78+
// Handle the message here...
79+
}
80+
}
81+
82+
// Register that specific message...
83+
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this);
84+
85+
// ...or alternatively, register all declared handlers
86+
WeakReferenceMessenger.Default.RegisterAll(this);
87+
88+
// Send a message from some other module
89+
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
90+
```
6491

6592
## Using request messages
6693

@@ -73,13 +100,16 @@ public class LoggedInUserRequestMessage : RequestMessage<User>
73100
}
74101

75102
// Register the receiver in a module
76-
Messenger.Default.Register<LoggedInUserRequestMessage>(this, m =>
103+
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
77104
{
78-
m.Reply(CurrentUser); // Assume this is a private member
105+
// Assume that "CurrentUser" is a private member in our viewmodel.
106+
// As before, we're accessing it through the recipient passed as
107+
// input to the handler, to avoid capturing "this" in the delegate.
108+
m.Reply(r.CurrentUser);
79109
});
80110

81111
// Request the value from another module
82-
User user = Messenger.Default.Send<LoggedInUserRequestMessage>();
112+
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();
83113
```
84114

85115
The `RequestMessage<T>` class includes an implicit converter that makes the conversion from a `LoggedInUserRequestMessage` to its contained `User` object possible. This will also check that a response has been received for the message, and throw an exception if that's not the case. It is also possible to send request messages without this mandatory response guarantee: just store the returned message in a local variable, and then manually check whether a response value is available or not. Doing so will not trigger the automatic exception if a response is not received when the `Send` method returns.
@@ -94,13 +124,13 @@ public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
94124
}
95125

96126
// Register the receiver in a module
97-
Messenger.Default.Register<LoggedInUserRequestMessage>(this, m =>
127+
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
98128
{
99-
m.Reply(GetCurrentUserAsync()); // We're replying with a Task<User>
129+
m.Reply(r.GetCurrentUserAsync()); // We're replying with a Task<User>
100130
});
101131

102132
// Request the value from another module (we can directly await on the request)
103-
User user = await Messenger.Default.Send<LoggedInUserRequestMessage>();
133+
User user = await WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();
104134
```
105135

106136
## Sample Code
@@ -116,5 +146,6 @@ There are more examples in the [unit tests](https://github.com/Microsoft/Windows
116146

117147
## API
118148

119-
* [Messenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs)
120149
* [IMessenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs)
150+
* [StrongReferenceMessenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/StrongReferenceMessenger.cs)
151+
* [WeakReferenceMessenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/WeakReferenceMessenger.cs)

0 commit comments

Comments
 (0)