Notas de estudio de CommunityToolkit.Mvvm (4)——Messenger

Descripción general de Messenger

Si tiene cierto conocimiento de WPF, debe saber que un comando en WPF es una clase que implementa la interfaz ICommand. De manera similar, aunque el título de este artículo es Messenger, también comienza con la interfaz IMessenger. En cuanto al nombre chino de Messenger, creo que es mejor llamarlo literalmente "mensajero", después de todo, entregar mensajes es la capacidad de un mensajero.

1 interfaz de mensajería instantánea

Espacio de nombres: Microsoft.Toolkit.Mvvm.Messaging
Ensamblaje: Microsoft.Toolkit.Mvvm.dll
Paquete: Microsoft.Toolkit.Mvvm

La interfaz IMessenger proporciona la capacidad que deberían tener las clases que intercambian mensajes entre diferentes objetos, lo que es muy útil para desacoplar diferentes módulos de un programa sin tener que mantener referencias fuertes a tipos de referencia .

强引用:
当应用程序的代码能够访问到程序正在使用的对象时,GC(.NET平台的垃圾收集器)就不能回收该对象。
就称程序对该对象进行了强引用。
强引用和这有什么关系呢?
如果结合“解耦程序”来理解就会明了许多。
不关心代码能不能访问到该对象,我一样可以交换消息。

También puedes enviar mensajes a canales designados (identificados de forma única mediante un token) y hacer que diferentes mensajeros permanezcan en diferentes partes del programa.

Para utilizar la funcionalidad IMessenger , primero defina una clase de mensaje, como esta:

// 定义一个登陆完成的消息
//(这里为了说明步骤而不是谈具体实现,所以只是定义了一个普通的class,
// 实际上它应该继承并实现IMessenger接口,不过MVVM工具包为你提供现成的类,后面会提到)
public sealed class LoginCompletedMessage {
    
     }
// sealed关键词会防止其它类继承该类
// 至于为什么加这个关键词,因为例子里加了

A continuación, registre un receptor para el mensaje:

Messenger.Default.Register<MyRecipientType, LoginCompletedMessage>(this, (r, m) =>
{
    
    
    // Handle the message here...
});

El procesador de mensajes aquí es una expresión lambda con dos parámetros: receptor (r, destinatario) y mensaje (m, mensaje). Esto se hace para evitar asignar cierres (puede aprender esta palabra en Baidu y es muy común en expresiones lambda). Si la expresión captura la instancia actual, se generará un cierre. El receptor se utiliza como parámetro para que se pueda acceder a él directamente en el controlador sin la necesidad de realizar una conversión de tipo manual.

xx_handler(object sender, Args e)
{
     
     
	// 这类用法大家一定不陌生,需要你手动将object转换类型
	(sender as xxType).Method();
}

Usar el receptor como parámetro hace que el código sea menos redundante y más confiable porque todas las comprobaciones se realizan en el momento de la compilación.

Si el controlador está definido en la misma clase que el receptor, también puede acceder directamente a miembros privados. Esto permite que el controlador de mensajes sea un método estático, lo que permite al compilador de C# realizar una serie de optimizaciones de memoria adicionales (como el almacenamiento en caché de delegados para evitar asignaciones de memoria innecesarias). Finalmente, envíe el mensaje cuando sea necesario, así:

Messenger.Default.Send<LoginCompletedMessage>();

Además, la sintaxis del grupo de métodos se puede utilizar para especificar un controlador de mensajes que se llamará cuando se reciba un mensaje, siempre que haya un método con la firma correcta disponible en el ámbito actual. Esto ayuda a mantener separadas la lógica de registro y procesamiento. Basándose en el ejemplo anterior, considere una clase con el siguiente método:

private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
{
    
    
    // Handle the message there
}

Se registra el siguiente código:

Messenger.Default.Register(this, Receive);

El compilador de C# convierte automáticamente la expresión en una instancia de MessageHandler<TRecipient,TMessage> que es compatible con Register<TRecipient,TMessage>(IMessenger, TRecipient, MessageHandler<TRecipient,TMessage>) . Si hay varias sobrecargas del método disponibles, cada una manejando un tipo de mensaje diferente: el compilador de C# elegirá automáticamente la apropiada según la clase de mensaje actual. También puede registrar explícitamente un controlador de mensajes mediante la interfaz IRecipient<TMessage> . Para hacer esto, el receptor solo necesita implementar la interfaz y luego llamar a la extensión RegisterAll(IMessenger, Object) , que registrará automáticamente todos los controladores declarados por el tipo de receptor. Por supuesto, también se admite sólo un procesador.

La siguiente es la interfaz de IMessenger.
Insertar descripción de la imagen aquí
IMessenger tiene dos clases derivadas:

  • Microsoft.Toolkit.Mvvm.Messaging.StrongReferenceMessenger
  • Microsoft.Toolkit.Mvvm.Messaging.WeakReferenceMessenger

Estas dos clases son dos implementaciones listas para usar proporcionadas por el kit de herramientas MVVM (es decir, IMessenger se ha implementado para usted).

  • El primero utiliza referencias débiles internamente para proporcionar una gestión automática de la memoria al receptor.
  • Este último utiliza referencias sólidas y requiere que el desarrollador cancele manualmente la suscripción del receptor (cuando el receptor ya no sea necesario). Pero a cambio, proporciona un mejor rendimiento y un menor uso de memoria.

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

2 Cómo funciona Messenger

La clase que implementa la interfaz IMessenger es responsable de mantener el vínculo entre el receptor del mensaje (destinatario) y la clase de mensaje registrada, así como los controladores de mensajes relacionados (controladores). Cualquier objeto puede registrarse como receptor de una clase de mensaje específica utilizando un controlador de mensajes, que se llama cada vez que se envía un mensaje de esta clase utilizando una instancia de IMessenger . Los mensajes también se pueden enviar a través de un canal de comunicación específico (identificado por un token único), de modo que varios módulos puedan intercambiar el mismo tipo de mensajes sin causar conflictos. Los mensajes enviados sin token utilizan el canal compartido predeterminado.

Hay dos formas de registrar mensajes:
una es usar la interfaz IRecipient<TMessage> y la otra es usar el delegado MessageHandler<TRecipient, TMessage> como controlador de mensajes.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
El primer método le permite registrar todos los controladores utilizando el método de extensión RegisterAll , que registrará automáticamente todos los receptores que declaren controladores de mensajes. Este último es más adecuado cuando necesita una expresión lambda más flexible o simple como procesador de mensajes.

Tanto WeakReferenceMessenger como StrongReferenceMessenger exponen el atributo Default , que proporciona una implementación segura para subprocesos integrada en el paquete. Si es necesario, también puede crear varias instancias de mensajería, por ejemplo, utilizar un proveedor de servicios DI para inyectar diferentes instancias en diferentes módulos del programa (por ejemplo, cuando se ejecutan varias ventanas en el mismo proceso).

Nota:
Debido a que WeakReferenceMessenger es más sencillo de usar y coincide mejor con el comportamiento del mensajero en la biblioteca MvvmLight ,
es el tipo de uso predeterminado de ObservableRecipient en MVVM Toolkit. StrongReferenceMessenger también está disponible
pasando una instancia al constructor de la clase .

3 Enviar y recibir mensajes

Considere el siguiente código:

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

Supongamos que la clase de mensaje se usa en un programa de mensajería simple (software de chat) que muestra una barra de título con el nombre de usuario y la imagen de perfil del usuario actualmente conectado, un panel con la lista de chat y otro panel con el Panel de chat actual. mensajes (con uno seleccionado). Supongamos que estas tres partes son compatibles con HeaderViewModel , ConversationsListViewModel y ConversationViewModel respectivamente . En este escenario, una vez completada la operación de inicio de sesión, HeaderViewModel envía el mensaje LoggedInUserChangedMessage y los otros dos modelos de vista registran controladores para el mensaje. Por ejemplo, ConversationsListViewModel cargará la lista de conversaciones de un nuevo usuario, mientras que ConversationViewModel cerrará la conversación actual, si existe.

La instancia de IMessenger es responsable de distribuir mensajes a todos los destinatarios registrados. Tenga en cuenta que los receptores pueden suscribirse a tipos específicos de mensajes. Una cosa más a tener en cuenta es que la clase de mensaje heredada no está registrada con la implementación predeterminada de IMessenger proporcionada por MVVM Toolkit.

Cuando un destinatario ya no es necesario, debes cancelar su registro para que deje de recibir mensajes. Puede cancelar su registro mediante clase de mensaje, token de registro o receptor:

// 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ó anteriormente, la operación de cancelación del registro anterior no es estrictamente necesaria cuando se usa WeakReferenceMessenger, porque el uso de referencias débiles para rastrear receptores significa que el GC aún limpiará los receptores no utilizados incluso si todavía tienen controladores de mensajes activos . Sin embargo, sigue siendo una buena práctica cancelar su suscripción, lo que puede mejorar el rendimiento.

Por otro lado, la implementación de StrongReferenceMessenger utiliza referencias sólidas para realizar un seguimiento de los receptores registrados. Esto se hace por motivos de rendimiento, lo que significa que cada receptor registrado debe cancelarse manualmente para evitar pérdidas de memoria. Es decir, siempre que un receptor esté registrado, la instancia de StrongReferenceMessenger en uso mantendrá viva una referencia a él, lo que evitará que el GC recicle la instancia. Puede manejarlo manualmente o puede heredar de ObservableRecipient, que elimina automáticamente los registros de mensajes de todos los receptores de forma predeterminada cuando está deshabilitado.

También puede utilizar la interfaz IRecipient<TMessage> para registrar controladores de mensajes. En este caso, cada receptor debe implementar la interfaz de la clase de mensaje dada y proporcionar un método Recibir (TMessage) , que se llamará al recibir el mensaje, como se muestra a continuación:

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

4 Utilice mensajes de solicitud

Otra característica útil de las instancias de Messenger es que se pueden utilizar para solicitar valores de un módulo a otro. Para hacer esto, el paquete contiene una clase base RequestMessage<T> , que se usa 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> contiene un convertidor implícito que permite convertir una sesión de LoggedInUserRequestMessage al objeto Usuario que la contiene . Esto también comprobará si se ha recibido una respuesta al mensaje y, en caso contrario, generará una excepción. También es posible enviar mensajes de solicitud sin garantías de respuesta obligatorias: simplemente almacene el mensaje devuelto en una variable local y verifique manualmente si el valor de respuesta está disponible. Hacerlo no desencadenará una excepción automática si no se recibe respuesta cuando regresa el método Enviar.

El espacio de nombres también incluye mensajes de solicitud básicos para otros escenarios:
AsyncRequestMessage<T>, CollectionRequestMessage<T> y AsyncCollectionRequestMessage<T> . A continuación se muestra un ejemplo del uso de mensajes de solicitud asincrónicos:

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

Supongo que te gusta

Origin blog.csdn.net/BadAyase/article/details/125128698
Recomendado
Clasificación