Condividi tramite


Messenger

L'interfaccia IMessenger è un contratto per i tipi che possono essere usati per scambiare messaggi tra oggetti diversi. Ciò può essere utile per separare moduli diversi di un'applicazione senza dover mantenere riferimenti sicuri ai tipi a cui si fa riferimento. È anche possibile inviare messaggi a canali specifici, identificati in modo univoco da un token, e avere messenger diversi in diverse sezioni di un'applicazione. MVVM Toolkit offre due implementazioni predefinite: WeakReferenceMessenger e StrongReferenceMessenger: la prima usa riferimenti deboli internamente, offrendo la gestione automatica della memoria per i destinatari, mentre quest'ultima usa riferimenti sicuri e richiede agli sviluppatori di annullare manualmente la sottoscrizione dei destinatari quando non sono più necessari (altri dettagli su come annullare la registrazione dei gestori di messaggi sono disponibili di seguito), ma in cambio di ciò offre prestazioni migliori e un utilizzo della memoria molto inferiore.

API della piattaforma:IMessenger, WeakReferenceMessenger, StrongReferenceMessenger, IRecipient<TMessage>, MessageHandler<TRecipient, TMessage>CollectionRequestMessage<T>ObservableRecipientRequestMessage<T>AsyncRequestMessage<T>AsyncCollectionRequestMessage<T>.

Funzionamento

I tipi che implementano IMessenger sono responsabili della gestione dei collegamenti tra i destinatari (ricevitori di messaggi) e i relativi tipi di messaggio registrati, con gestori di messaggi relativi. Qualsiasi oggetto può essere registrato come destinatario per un determinato tipo di messaggio usando un gestore di messaggi, che verrà richiamato ogni volta che l'istanza IMessenger viene usata per inviare un messaggio di tale tipo. È anche possibile inviare messaggi tramite canali di comunicazione specifici (ognuno identificato da un token univoco), in modo che più moduli possano scambiare messaggi dello stesso tipo senza causare conflitti. I messaggi inviati senza un token usano il canale condiviso predefinito.

Esistono due modi per eseguire la registrazione dei messaggi: tramite l'interfaccia IRecipient<TMessage> o usando un MessageHandler<TRecipient, TMessage> delegato che funge da gestore messaggi. Il primo consente di registrare tutti i gestori con una singola chiamata all'estensione RegisterAll , che registra automaticamente i destinatari di tutti i gestori di messaggi dichiarati, mentre quest'ultimo è utile quando è necessaria maggiore flessibilità o quando si vuole usare una semplice espressione lambda come gestore di messaggi.

Sia WeakReferenceMessenger che StrongReferenceMessenger espongono anche una Default proprietà che offre un'implementazione thread-safe incorporata nel pacchetto. È anche possibile creare più istanze di messenger, se necessario, ad esempio se ne viene inserito uno diverso con un provider di servizi di inserimento delle dipendenze in un modulo diverso dell'app (ad esempio, più finestre in esecuzione nello stesso processo).

Nota

Poiché il WeakReferenceMessenger tipo è più semplice da usare e corrisponde al comportamento del tipo messenger dalla MvvmLight libreria, è il tipo predefinito usato dal ObservableRecipient tipo in MVVM Toolkit. È StrongReferenceType comunque possibile usare , passando un'istanza al costruttore di tale classe.

Invio e ricezione di messaggi

Considerare quanto segue:

// 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));

Si supponga che questo tipo di messaggio venga usato in una semplice applicazione di messaggistica, che visualizza un'intestazione con il nome utente e l'immagine del profilo dell'utente attualmente registrato, un pannello con un elenco di conversazioni e un altro pannello con messaggi della conversazione corrente, se selezionato. Si supponga che queste tre sezioni siano supportate rispettivamente dai HeaderViewModeltipi e ConversationsListViewModelConversationViewModel . In questo scenario, il LoggedInUserChangedMessage messaggio potrebbe essere inviato da dopo il completamento di HeaderViewModel un'operazione di accesso e entrambi gli altri modelli di visualizzazione potrebbero registrare i gestori. Ad esempio, ConversationsListViewModel caricherà l'elenco delle conversazioni per il nuovo utente e ConversationViewModel chiuderà solo la conversazione corrente, se presente.

L'istanza IMessenger si occupa del recapito dei messaggi a tutti i destinatari registrati. Si noti che un destinatario può sottoscrivere messaggi di un tipo specifico. Si noti che i tipi di messaggio ereditati non vengono registrati nelle implementazioni predefinite IMessenger fornite da MVVM Toolkit.

Quando un destinatario non è più necessario, è necessario annullare la registrazione in modo da interrompere la ricezione dei messaggi. È possibile annullare la registrazione in base al tipo di messaggio, al token di registrazione o al destinatario:

// 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);

Avviso

Come accennato in precedenza, questo non è strettamente necessario quando si usa il WeakReferenceMessenger tipo, poiché usa riferimenti deboli per tenere traccia dei destinatari, ovvero i destinatari inutilizzati saranno comunque idonei per l'operazione di Garbage Collection anche se hanno ancora gestori messaggi attivi. È comunque buona norma annullarli, per migliorare le prestazioni. D'altra parte, l'implementazione StrongReferenceMessenger usa riferimenti sicuri per tenere traccia dei destinatari registrati. Questa operazione viene eseguita per motivi di prestazioni e significa che ogni destinatario registrato deve essere annullata manualmente per evitare perdite di memoria. Ciò significa che, purché un destinatario sia registrato, l'istanza StrongReferenceMessenger in uso manterrà un riferimento attivo, impedendo al Garbage Collector di raccogliere tale istanza. È possibile gestire questa operazione manualmente oppure ereditare da ObservableRecipient, che per impostazione predefinita si occupa automaticamente della rimozione di tutte le registrazioni dei messaggi per il destinatario quando viene disattivata (vedere la documentazione su ObservableRecipient per altre informazioni su questo).

È anche possibile usare l'interfaccia IRecipient<TMessage> per registrare i gestori di messaggi. In questo caso, ogni destinatario dovrà implementare l'interfaccia per un determinato tipo di messaggio e fornire un Receive(TMessage) metodo che verrà richiamato durante la ricezione di messaggi, come illustrato di seguito:

// 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));

Uso dei messaggi di richiesta

Un'altra funzionalità utile delle istanze di Messenger è che possono essere usate anche per richiedere valori da un modulo a un altro. A tale scopo, il pacchetto include una classe base RequestMessage<T> , che può essere usata come segue:

// 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>();

La RequestMessage<T> classe include un convertitore implicito che rende possibile la conversione da un LoggedInUserRequestMessage oggetto al relativo oggetto contenuto User . Ciò verificherà anche che sia stata ricevuta una risposta per il messaggio e generi un'eccezione se non è così. È anche possibile inviare messaggi di richiesta senza questa garanzia di risposta obbligatoria: archiviare semplicemente il messaggio restituito in una variabile locale e quindi controllare manualmente se un valore di risposta è disponibile o meno. In questo modo non verrà attivata l'eccezione automatica se non viene ricevuta una risposta quando viene restituito il Send metodo .

Lo stesso spazio dei nomi include anche il messaggio di richieste di base per altri scenari: AsyncRequestMessage<T>e CollectionRequestMessage<T>AsyncCollectionRequestMessage<T>. Ecco come usare un messaggio di richiesta asincrono:

// 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>();

Esempi