CommunityToolkit.Mvvm学习笔记(4)——Messenger

Messenger概述

如果你对WPF有一定了解,你应该知道WPF中的命令是一个实现了ICommand接口的类。同样本文虽然标题是Messenger,但也要从IMessenger接口说起。至于Messenger的中文名,我觉得就叫它的直译“信使”好了,毕竟传递消息就是信使的能力嘛。

1 IMessenger接口

命名空间:Microsoft.Toolkit.Mvvm.Messaging
程序集:Microsoft.Toolkit.Mvvm.dll
包:Microsoft.Toolkit.Mvvm

IMessenger接口提供了不同对象间交换消息的类应该具有的能力,这对解耦程序的不同模块非常有用,而不必持有引用类型的强引用

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

你也可以将消息发送到指定通道(通过令牌唯一标识,uniquely identified by a token),让不同的信使呆在程序的不同部分。

要使用 IMessenger 功能,首先定义一个消息类,像这样:

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

接着,为该消息注册一个接收者(Recipient):

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

这里的消息处理器是一个有两个参数的lambda表达式:接收者(r,recipient)和消息(m,message)。这么做是为了避免分配闭包(这个词可以百度了解一下,在lambda表达式中很常见),如果表达式捕获了当前实例,就会生成闭包。将接收者作为参数,是为了能在处理程序中直接访问它,而不需要手动进行类型转换。

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

将接收者作为参数能使代码冗余更少,更可靠,因为所有的检查在构建时就完成了。

如果处理器(handler)定义在与接收者相同的类中,那它也可以直接访问私有成员。这允许消息处理器是一个静态方法,这使得C#编译器能执行一系列额外的内存优化(例如缓存委托,避免不必要的内存分配)。最后,在需要时发送消息,就像这样:

扫描二维码关注公众号,回复: 16978111 查看本文章
Messenger.Default.Send<LoginCompletedMessage>();

此外,若当前作用域内有可用的带有正确签名的方法,方法组语法还可用于指定在接收消息时调用的消息处理器。这有助于使注册和处理逻辑分离。在上个例子的基础上,考虑一个带有以下方法的类:

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

以下代码进行注册:

Messenger.Default.Register(this, Receive);

C#编译器自动将表达式转化为一个与 Register<TRecipient,TMessage>(IMessenger, TRecipient, MessageHandler<TRecipient,TMessage>) 兼容的 MessageHandler<TRecipient,TMessage> 实例。如果该方法有多个重载可用,每个重载处理着不同的消息类型:C#编译器将自动根据当前消息类选择一个合适的。也可以显式注册使用 IRecipient<TMessage> 接口的消息处理器。若这样做,接收者只需要实现该接口,然后调用 RegisterAll(IMessenger, Object) 扩展,该拓展将会自动注册由接收者类型所声明的所有处理器。当然,只有一个处理器也支持。

下面是IMessenger接口,
在这里插入图片描述
IMessenger有两个派生类:

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

这两个类是MVVM工具包提供的两种即用的实现(就是已经给你实现好了IMessenger),

  • 前者在内部使用的是弱引用,为接收者提供自动内存管理
  • 后者使用强引用,要求开发者(在不再需要接收者时)手动取消接收者的订阅。但作为交换,它提供了更好的性能和更少的内存使用。

相关的平台API:IMessenger, WeakReferenceMessenger, StrongReferenceMessenger, IRecipient<TMessage>, MessageHandler<TRecipient, TMessage>, ObservableRecipient, RequestMessage<T>, AsyncRequestMessage<T>, CollectionRequestMessage<T>, AsyncCollectionRequestMessage<T>.

2 信使的工作原理

实现了 IMessenger 接口的类负责维护消息接收者(recipient)和注册消息类之间的链接(link),以及相关的消息处理器(handlers)。任何对象都可以使用消息处理器注册为指定消息类的接收者,每当使用 IMessenger 实例发送该类的消息时,消息处理器会被调用。也可以通过特定通信通道(由唯一令牌标识)发送消息,这样多个模块间可以交换相同类型的消息,而不会引起冲突。不使用令牌发送的消息使用默认共享通道。

有两种注册消息的方式:
一种是使用 IRecipient<TMessage> 接口,另一种是用 MessageHandler<TRecipient, TMessage> 委托来作为消息处理器。
在这里插入图片描述
在这里插入图片描述
第一种方法,允许你用 RegisterAll 扩展方法来注册所有处理器,它会自动注册所有声明消息处理器的接收者。而后者在你需要更灵活或者简单的lambda表达式作为消息处理器时更适用。

WeakReferenceMessengerStrongReferenceMessenger 均暴露了 Default 属性,该属性提供了一个内置于包中的线程安全实现。如果需要,也可以创建多个messenger实例,例如,将不同的实例用DI服务提供者(Service provider)注入到程序不同的模块中(如,多个窗口运行在同一个进程中时)。

注意:
由于 WeakReferenceMessenger 用起来更简单,并与 MvvmLight 库中的messenger的行为更匹配,
所以它是MVVM Toolkit中 ObservableRecipient 默认的使用类型。
通过传递实例给该类的构造函数,StrongReferenceMessenger 也仍然可用。

3 收发消息

考虑以下代码:

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

假设在一个简单的消息通信程序(聊天软件)中使用该消息类,该程序显示一个带有当前登录用户的用户名和简介图像的标题栏,一个带有聊天列表的面板,以及另一个带有当前聊天消息的面板(选中了其中一个)。假设这三个部分分别由 HeaderViewModel , ConversationsListViewModelConversationViewModel 支持。在该场景中,登录操作完成后, LoggedInUserChangedMessage 消息会由 HeaderViewModel 发送,并且其他两个viewmodel会为该消息注册处理器。例如,ConversationsListViewModel 将为新用户加载对话列表,而 ConversationViewModel 将关闭当前对话(如果存在的话)。

IMessenger 实例负责将消息分发给所有已注册的接收者。注意,接收者可以订阅指定类型的消息。还有一点要注意,继承的消息类没有在MVVM Toolkit提供的默认IMessenger实现中注册。

当不再需要某个接收者时,你应该注销它,使其停止接收消息。你可以通过消息类、注册令牌或接收者来注销它:

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

警告:
正如前面所述,当使用 WeakReferenceMessenger 时,上面的注销操作不是严格需要的,因为使用弱引用来追踪接收者意味着不用的接收者即使仍然有激活的消息处理程序,它们仍会被GC清理。不过,取消订阅它们仍然是一个好的做法,这可以提高性能。

另一方面,StrongReferenceMessenger 实现使用了强引用来跟踪注册的接收者。这样做是出于性能考虑,这意味着每个注册的接收者应该手动被注销以避免内存泄漏。也就是说,只要注册了一个接收者,使用中StrongReferenceMessenger实例就会保活对它的引用,这将防止GC回收该实例。你可以手动处理它,也可以从ObservableRecipient继承,当它被禁用时,默认情况下会自动删除所有接收者的消息注册。

你也可以使用 IRecipient<TMessage> 接口来注册消息处理器。这种情况下,每个接收者需要实现给定消息类的接口,并提供一个 Receive(TMessage) 方法,该方法会在接收消息时被调用,如下所示:

// 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 使用请求消息

messenger实例另一个有用的特性是,它可以用于从一个模块向另一个模块请求值。要做到这点,该包包含了一个 RequestMessage<T> 基类,如下使用:

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

RequestMessage<T> 类包含了一个隐式转换器,能使会话从 LoggedInUserRequestMessage 到其包含的 User 对象成为可能。这也将检查是否收到了消息的响应,若没有收到,则抛出异常。也可以在没有强制响应保证的情况下发送请求消息:只需将返回的消息存储在本地变量中,然后手动检查响应值是否可用。如果Send方法返回时没有收到响应,那么这样做不会触发自动异常。

该命名空间还包括用于其他场景的基础请求消息:
AsyncRequestMessage<T>, CollectionRequestMessage<T> 和 AsyncCollectionRequestMessage<T> 。下面是一个使用异步请求消息的例子:

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

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/125128698