Compartir a través de


Mensajero

La interfaz IMessenger es un contrato para los tipos que se pueden usar para intercambiar mensajes entre distintos objetos. Esto puede ser útil para desacoplar diferentes módulos de una aplicación sin tener que mantener referencias seguras a los tipos a los que se hace referencia. También es posible enviar mensajes a canales específicos, identificados de forma única por un token, y tener mensajeros diferentes en diferentes secciones de una aplicación. El kit de herramientas de MVVM proporciona dos implementaciones de fábrica: WeakReferenceMessenger y StrongReferenceMessenger: la primera usa referencias débiles internamente, ofreciendo administración automática de memoria para destinatarios, mientras que esta última usa referencias seguras y requiere que los desarrolladores cancelen manualmente la suscripción de sus destinatarios cuando ya no sean necesarios (más detalles sobre cómo anular el registro de controladores de mensajes se pueden encontrar a continuación), pero a cambio de eso ofrece un mejor rendimiento y mucho menos uso de memoria.

API de la plataforma: IMessenger, WeakReferenceMessenger, StrongReferenceMessenger, IRecipient<TMessage>, MessageHandler<TRecipient, TMessage>, ObservableRecipient, RequestMessage<T>, AsyncRequestMessage<T>, CollectionRequestMessage<T>, AsyncCollectionRequestMessage<T>.

Funcionamiento

Los tipos que implementan IMessenger son responsables de mantener vínculos entre destinatarios (receptores de mensajes) y sus tipos de mensajes registrados, con controladores de mensajes relativos. Cualquier objeto se puede registrar como destinatario para un tipo de mensaje determinado mediante un controlador de mensajes, que se invocará siempre que se use la instancia IMessenger para enviar un mensaje de ese tipo. También es posible enviar mensajes a través de canales de comunicación específicos (cada uno identificado por un token único), de modo que varios módulos puedan intercambiar mensajes del mismo tipo sin causar conflictos. Los mensajes enviados sin un token usan el canal compartido predeterminado.

Hay dos maneras de realizar el registro de mensajes: a través de la interfaz IRecipient<TMessage> o mediante un delegado MessageHandler<TRecipient, TMessage> que actúa como controlador de mensajes. La primera permite registrar todos los controladores con una sola llamada a la extensión RegisterAll, que registra automáticamente los destinatarios de todos los controladores de mensajes declarados, mientras que este último resulta útil cuando necesita más flexibilidad o cuando desea usar una expresión lambda simple como controlador de mensajes.

Tanto WeakReferenceMessenger y StrongReferenceMessenger también exponen una propiedad Default que ofrece una implementación segura para subprocesos integrada en el paquete. También es posible crear varias instancias de mensajes si es necesario, por ejemplo, si se inserta otra con un proveedor de servicios de inserción de dependencias en un módulo diferente de la aplicación (por ejemplo, varias ventanas que se ejecutan en el mismo proceso).

Nota:

Dado que el tipo WeakReferenceMessenger es más sencillo de usar y coincide con el comportamiento del tipo mensaje de la biblioteca MvvmLight, es el tipo predeterminado que usa el tipo ObservableRecipient en el kit de herramientas de MVVM. El StrongReferenceType todavía se puede usar, pasando una instancia al constructor de esa clase.

Envío y recepción de mensajes

Tenga en cuenta lo siguiente.

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

Imaginemos que este tipo de mensaje se usa en una aplicación de mensajería sencilla, que muestra un encabezado con el nombre de usuario y la imagen de perfil del usuario registrado actualmente, un panel con una lista de conversaciones y otro panel con mensajes de la conversación actual, si se selecciona uno. Supongamos que estas tres secciones son compatibles con los tipos HeaderViewModel, ConversationsListViewModel y ConversationViewModel respectivamente. En este escenario, el HeaderViewModel puede enviar el mensaje LoggedInUserChangedMessage una vez completada una operación de inicio de sesión, y ambos modelos de vista pueden registrar controladores para él. Por ejemplo, ConversationsListViewModel cargará la lista de conversaciones para el nuevo usuario y ConversationViewModel simplemente cerrará la conversación actual, si hay una presente.

La instancia IMessenger se encarga de entregar mensajes a todos los destinatarios registrados. Tenga en cuenta que un destinatario puede suscribirse a mensajes de un tipo específico. Tenga en cuenta que los tipos de mensajes heredados no están registrados en las implementaciones IMessenger predeterminadas proporcionadas por el kit de herramientas de MVVM.

Cuando ya no se necesite un destinatario, debe anular el registro para que deje de recibir mensajes. Puede anular el registro por tipo de mensaje, por token de registro o por 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);

Advertencia

Como se mencionó antes, esto no es estrictamente necesario cuando se usa el tipo WeakReferenceMessenger, ya que usa referencias débiles para realizar un seguimiento de los destinatarios, lo que significa que los destinatarios no usados seguirán siendo aptos para la recolección de elementos no utilizados aunque todavía tengan controladores de mensajes activos. Sin embargo, sigue siendo recomendable cancelar su suscripción para mejorar los rendimientos. Por otro lado, la implementación StrongReferenceMessenger usa referencias seguras para realizar un seguimiento de los destinatarios registrados. Esto se hace por motivos de rendimiento y significa que cada destinatario registrado debe anularse manualmente para evitar pérdidas de memoria. Es decir, siempre que se registre un destinatario, la instancia StrongReferenceMessenger en uso mantendrá una referencia activa a él, lo que impedirá que el recolector de elementos no utilizados pueda recopilar esa instancia. Puede controlar esto manualmente o puede heredar de ObservableRecipient, que de forma predeterminada se encarga de quitar todos los registros de mensajes para el destinatario cuando se desactiva (vea los documentos en ObservableRecipient para obtener más información sobre esto).

También es posible usar la interfaz IRecipient<TMessage> para registrar controladores de mensajes. En este caso, cada destinatario deberá implementar la interfaz para un tipo de mensaje determinado y proporcionar un método Receive(TMessage) que se invocará al recibir mensajes, de la siguiente manera:

// 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 de mensajes de solicitud

Otra característica útil de las instancias de mensajes es que también se pueden usar para solicitar valores de un módulo a otro. Para ello, el paquete incluye una clase base RequestMessage<T>, que se puede usar de la siguiente manera:

// 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 clase RequestMessage<T> incluye un convertidor implícito que hace posible la conversión de un LoggedInUserRequestMessage a su objeto User contenido. Esto también comprobará que se ha recibido una respuesta para el mensaje y producirá una excepción si no es así. También es posible enviar mensajes de solicitud sin esta garantía de respuesta obligatoria: solo tiene que almacenar el mensaje devuelto en una variable local y, a continuación, comprobar manualmente si hay un valor de respuesta disponible o no. Si no lo hace, no se desencadenará la excepción automática si no se recibe una respuesta cuando se devuelve el método Send.

El mismo espacio de nombres también incluye el mensaje de solicitudes base para otros escenarios: AsyncRequestMessage<T>, CollectionRequestMessage<T> y AsyncCollectionRequestMessage<T>. Aquí se muestra cómo puede usar un mensaje de solicitud asincrónica:

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

Ejemplos

  • Consulte la aplicación de ejemplo (para varios marcos de interfaz de usuario) para ver el kit de herramientas de MVVM en acción.
  • También puede encontrar más ejemplos en las pruebas unitarias.