Messenger
Die Schnittstelle IMessenger
ist ein Vertrag für Typen, die für den Austausch von Meldungen zwischen verschiedenen Objekten verwendet werden können. Dies kann hilfreich sein, um verschiedene Module einer Anwendung zu entkoppeln, ohne starke Verweise auf Typen beizubehalten, auf die verwiesen wird. Es ist auch möglich, Meldungen an bestimmte Kanäle zu senden, die durch ein Token eindeutig identifiziert werden, und verschiedene Messenger in verschiedenen Bereichen einer Anwendung einzusetzen. Das MVVM Toolkit stellt zwei Implementierungen zur Verfügung: WeakReferenceMessenger
und StrongReferenceMessenger
: Erstere verwendet intern schwache Referenzen und bietet eine automatische Speicherverwaltung für die Empfänger, während letztere starke Referenzen verwendet und von den Entwicklern und Entwicklerinnen verlangt, dass sie die Empfänger manuell abmelden, wenn sie nicht mehr benötigt werden (weitere Details zur Abmeldung von Meldungshandlern finden Sie weiter unten). Dafür bietet sie aber eine bessere Leistung und einen weitaus geringeren Speicherverbrauch.
Plattform-APIs:
IMessenger
,WeakReferenceMessenger
,StrongReferenceMessenger
,IRecipient<TMessage>
,MessageHandler<TRecipient, TMessage>
,ObservableRecipient
,RequestMessage<T>
,AsyncRequestMessage<T>
,CollectionRequestMessage<T>
,AsyncCollectionRequestMessage<T>
Funktionsweise
Typen, die IMessenger
implementieren, sind für die Pflege von Verknüpfungen zwischen Empfängern (Empfängern von Meldungen) und ihren registrierten Meldungstypen mit entsprechenden Meldungshandlern verantwortlich. Jedes Objekt kann als Empfänger für einen bestimmten Meldungstyp registriert werden, indem ein Meldungshandler verwendet wird, der immer dann aufgerufen wird, wenn die Instanz IMessenger
zum Senden einer Meldung dieses Typs verwendet wird. Es ist auch möglich, Meldungen über bestimmte Kommunikationskanäle zu senden (die jeweils durch ein eindeutiges Token identifiziert werden), so dass mehrere Module Meldungen desselben Typs austauschen können, ohne Konflikte zu verursachen. Meldungen, die ohne Token gesendet werden, verwenden den standardmäßigen gemeinsamen Kanal.
Es gibt zwei Möglichkeiten, die Meldungsregistrierung durchzuführen: entweder über die Schnittstelle IRecipient<TMessage>
oder über ein Delegat MessageHandler<TRecipient, TMessage>
, das als Meldungshandler fungiert. Mit der ersten können Sie alle Handler mit einem einzigen Aufruf der Erweiterung RegisterAll
registrieren, die automatisch die Empfänger aller deklarierten Meldungshandler registriert. Die zweite Variante ist nützlich, wenn Sie mehr Flexibilität benötigen oder wenn Sie einen einfachen Lambda-Ausdruck als Meldungshandler verwenden möchten.
Sowohl WeakReferenceMessenger
als auch StrongReferenceMessenger
machen auch eine Eigenschaft Default
verfügbar, die eine in das Paket integrierte, threadsichere Implementierung bietet. Es ist auch möglich, bei Bedarf mehrere Messenger-Instanzen zu erstellen, z. B. wenn ein anderer Messenger mit einem DI-Dienstanbieter in ein anderes Modul der App injiziert wird (z. B. mehrere Fenster, die im selben Prozess laufen).
Hinweis
Da der Typ WeakReferenceMessenger
einfacher zu verwenden ist und dem Verhalten des Messenger-Typs aus der Bibliothek MvvmLight
entspricht, ist er der Standardtyp, der vom Typ ObservableRecipient
im MVVM-Toolkit verwendet wird. Der StrongReferenceType
kann weiterhin verwendet werden, indem Sie dem Konstruktor dieser Klasse eine Instanz übergeben.
Senden und Empfangen von Nachrichten
Beachten Sie Folgendes:
// Create a message
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
public LoggedInUserChangedMessage(User user) : base(user)
{
}
}
// Register a message in some module
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
{
// Handle the message here, with r being the recipient and m being the
// input message. Using the recipient passed as input makes it so that
// the lambda expression doesn't capture "this", improving performance.
});
// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
Angenommen, dieser Meldungstyp wird in einer einfachen Messaging-Anwendung verwendet, die eine Kopfzeile mit dem Benutzernamen und dem Profilbild des aktuell angemeldeten Benutzerkontos, ein Panel mit einer Liste von Konversationen und ein weiteres Panel mit Meldungen aus der aktuellen Konversation anzeigt, sofern eine ausgewählt ist. Angenommen, diese drei Abschnitte werden jeweils von den Typen HeaderViewModel
, ConversationsListViewModel
und ConversationViewModel
unterstützt. In diesem Szenario wird möglicherweise die Meldung LoggedInUserChangedMessage
von HeaderViewModel
gesendet, nachdem ein Anmeldevorgang abgeschlossen wurde, und beide anderen Viewmodels könnten Handler dafür registrieren. Zum Beispiel lädt ConversationsListViewModel
die Liste der Unterhaltungen für das neue Benutzerkonto, und ConversationViewModel
schließt nur die aktuelle Unterhaltung, falls eine vorhanden ist.
Die Instanz IMessenger
kümmert sich um die Zustellung von Meldungen an alle registrierten Empfänger. Beachten Sie, dass ein Empfänger Meldungen eines bestimmten Typs abonnieren kann. Beachten Sie, dass geerbte Meldungstypen nicht in den standardmäßigen IMessenger
-Implementierungen registriert werden, die vom MVVM-Toolkit bereitgestellt werden.
Wenn ein Empfänger nicht mehr benötigt wird, sollten Sie die Registrierung aufheben, damit er keine Meldungen mehr empfängt. Sie können die Registrierung entweder nach Meldungstyp, Registrierungstoken oder Empfänger aufheben:
// Unregisters the recipient from a message type
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage>(this);
// Unregisters the recipient from a message type in a specified channel
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);
// Unregister the recipient from all messages, across all channels
WeakReferenceMessenger.Default.UnregisterAll(this);
Warnung
Wie bereits erwähnt, ist dies bei Verwendung des Typs WeakReferenceMessenger
nicht unbedingt erforderlich, da schwache Verweise zum Nachverfolgen von Empfängern verwendet werden, was bedeutet, dass nicht verwendete Empfänger trotzdem bei der automatischen Speicherbereinigung berücksichtigt werden, obwohl sie weiterhin über aktive Meldungshandler verfügen. Es empfiehlt sich jedoch, die jeweilige Registrierung aufzuheben, um die Leistung zu verbessern. Die Implementierung StrongReferenceMessenger
verwendet hingegen starke Verweise, um die registrierten Empfänger nachzuverfolgen. Dies geschieht aus Leistungsgründen, und es bedeutet, dass die Registrierung jedes Empfängers manuell aufgehoben werden sollte, um Speicherverluste zu vermeiden. Solange ein Empfänger registriert ist, behält die verwendete StrongReferenceMessenger
-Instanz einen aktiven Verweis darauf bei, wodurch verhindert wird, dass diese Instanz vom Garbage Collector berücksichtigt wird. Sie können dies entweder manuell tun, oder Sie können sie von ObservableRecipient
erben lassen, wodurch standardmäßig automatisch alle Meldungsregistrierungen für den Empfänger entfernt werden, wenn sie deaktiviert wird (weitere Informationen hierzu finden Sie in der Dokumentation zu ObservableRecipient
).
Es ist auch möglich, die Schnittstelle IRecipient<TMessage>
zum Registrieren von Meldungshandlern zu verwenden. In diesem Fall muss jeder Empfänger die Schnittstelle für einen bestimmten Meldungstyp implementieren und eine Methode Receive(TMessage)
bereitstellen, die beim Empfangen von Meldungen aufgerufen wird, z. B.:
// Create a message
public class MyRecipient : IRecipient<LoggedInUserChangedMessage>
{
public void Receive(LoggedInUserChangedMessage message)
{
// Handle the message here...
}
}
// Register that specific message...
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this);
// ...or alternatively, register all declared handlers
WeakReferenceMessenger.Default.RegisterAll(this);
// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
Verwenden von Anforderungsmeldungen
Ein weiteres nützliches Feature von Messenger-Instanzen besteht darin, dass sie auch verwendet werden können, um auf einem Modul Werte von einem anderen Modul anzufordern. Dazu enthält das Paket eine Basisklasse RequestMessage<T>
, die wie folgt verwendet werden kann:
// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}
// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
// Assume that "CurrentUser" is a private member in our viewmodel.
// As before, we're accessing it through the recipient passed as
// input to the handler, to avoid capturing "this" in the delegate.
m.Reply(r.CurrentUser);
});
// Request the value from another module
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();
Die Klasse RequestMessage<T>
enthält einen impliziten Konverter, der die Konvertierung von einem LoggedInUserRequestMessage
- in das darin enthaltene User
-Objekt möglich macht. Dadurch wird auch überprüft, ob eine Antwort für die Meldung empfangen wurde, und es wird eine Ausnahme ausgelöst, wenn dies nicht der Fall ist. Es ist auch möglich, Anforderungsmeldungen ohne diese obligatorische Antwortgarantie zu senden: Speichern Sie die zurückgegebene Meldung einfach in einer lokalen Variablen, und überprüfen Sie dann manuell, ob ein Antwortwert verfügbar ist oder nicht. Auf diese Weise wird die automatische Ausnahme nicht ausgelöst, wenn bei der Rückgabe der Methode Send
keine Antwort empfangen wird.
Derselbe Namespace enthält auch Basisanforderungsmeldungen für andere Szenarien: AsyncRequestMessage<T>
, CollectionRequestMessage<T>
und AsyncCollectionRequestMessage<T>
.
So können Sie eine asynchrone Anforderungsmeldung verwenden:
// Create a message
public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
{
}
// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
m.Reply(r.GetCurrentUserAsync()); // We're replying with a Task<User>
});
// Request the value from another module (we can directly await on the request)
User user = await WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();
Beispiele
- Sehen Sie sich die Beispiel-App (für mehrere Benutzeroberflächen-Frameworks) an, um das MVVM-Toolkit in Aktion zu sehen.
- Weitere Beispiele finden Sie auch in den Komponententests.