信使
IMessenger
接口是可用于在不同对象之间交换消息的类型协定。 这可用于分离应用程序的不同模块,而无需保留对所引用类型的强引用。 还可以将消息发送到特定通道,由令牌唯一标识,并在应用程序的不同部分中具有不同的信使。 MVVM 工具包提供两种现用的实现:WeakReferenceMessenger
和 StrongReferenceMessenger
:前者在内部使用弱引用,为收件人提供自动内存管理,而后者使用强引用,并要求开发人员在不再需要收件人时手动取消订阅收件人(有关如何注销消息处理程序的更多详细信息,可在下面找到),但这一点换来的是提供更好的性能,而且内存使用量要少得多。
平台 API:
IMessenger
、WeakReferenceMessenger
、StrongReferenceMessenger
、IRecipient<TMessage>
、MessageHandler<TRecipient, TMessage>
、ObservableRecipient
、RequestMessage<T>
、AsyncRequestMessage<T>
、CollectionRequestMessage<T>
、AsyncCollectionRequestMessage<T>
。
工作原理
实现 IMessenger
的类型负责维护收件人(消息接收方)与其已注册的邮件类型(包含相对消息处理程序)之间的链接。 可以使用消息处理程序将任何对象注册为给定邮件类型的收件人,每当使用 IMessenger
实例发送该类型的消息时,都会调用该对象。 还可以通过特定的通信通道道(每个通道都由唯一令牌标识)发送消息,以便多个模块可以交换同一类型的消息,而不会造成冲突。 在没有令牌的情况下发送的消息使用默认共享通道。
有两种方法可以执行消息注册:通过 IRecipient<TMessage>
接口或使用充当消息处理程序的 MessageHandler<TRecipient, TMessage>
委托。 第一个允许你向 RegisterAll
扩展的单个调用注册所有处理程序,该扩展会自动注册所有声明的消息处理程序的收件人,而后者在需要更多灵活性或想要将简单的 lambda 表达式用作消息处理程序时非常有用。
WeakReferenceMessenger
和 StrongReferenceMessenger
还公开一个 Default
属性,该属性提供内置于包中的线程安全实现。 如果需要,还可以创建多个信使实例,例如,如果使用 DI 服务提供程序将另一个信使实例注入到应用的不同模块(例如,在同一进程中运行的多个窗口)。
注意
由于 WeakReferenceMessenger
类型更易于使用,并且与 MvvmLight
库中的信使类型的行为匹配,因此它是 MVVM 工具包中 ObservableRecipient
类型使用的默认类型。 通过将实例传递给该类的构造函数,仍可使用 StrongReferenceType
。
发送和接收消息
考虑以下情况:
// 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
、ConversationsListViewModel
和 ConversationViewModel
类型支持。 在此方案中,LoggedInUserChangedMessage
消息可能在登录操作完成后由 HeaderViewModel
发送,并且这两个其他视图模型都可以为其注册处理程序。 例如,ConversationsListViewModel
将加载新用户的聊天列表,如果存在对话,ConversationViewModel
将仅关闭当前对话。
IMessenger
实例负责将消息传送到所有已注册收件人。 请注意,收件人可以订阅特定类型的邮件。 请注意,继承的消息类型未在 MVVM 工具包提供的默认 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
类型时,这并非严格必要,因为它使用弱引用来跟踪收件人,这意味着未使用的收件人仍有资格进行垃圾回收,即使它们仍然具有活动消息处理程序。 不过,取消订阅它们仍然是一个很好的提高性能的做法。 另一方面,StrongReferenceMessenger
实现使用强引用来跟踪已注册的收件人。 这是出于性能原因完成的,这意味着应手动取消注册每个已注册的收件人,以避免内存泄漏。 也就是说,只要注册收件人,正在使用的 StrongReferenceMessenger
实例就会保留对其的活动引用,这将阻止垃圾回收器能够收集该实例。 可以手动处理此内容,也可以从 ObservableRecipient
继承,默认情况下,其会自动处理停用收件人的所有消息注册(有关此内容的详细信息,请参阅有关 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));
使用请求消息
信使实例的另一个有用功能是,它们还可用于将值从模块请求到另一个模块。 为此,包包括一个基础 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>();