信使

IMessenger 接口是可用于在不同对象之间交换消息的类型协定。 这可用于分离应用程序的不同模块,而无需保留对所引用类型的强引用。 还可以将消息发送到特定通道,由令牌唯一标识,并在应用程序的不同部分中具有不同的信使。 MVVM 工具包提供两种现用的实现:WeakReferenceMessengerStrongReferenceMessenger:前者在内部使用弱引用,为收件人提供自动内存管理,而后者使用强引用,并要求开发人员在不再需要收件人时手动取消订阅收件人(有关如何注销消息处理程序的更多详细信息,可在下面找到),但这一点换来的是提供更好的性能,而且内存使用量要少得多。

平台 API:IMessengerWeakReferenceMessengerStrongReferenceMessengerIRecipient<TMessage>MessageHandler<TRecipient, TMessage>ObservableRecipientRequestMessage<T>AsyncRequestMessage<T>CollectionRequestMessage<T>AsyncCollectionRequestMessage<T>

工作原理

实现 IMessenger 的类型负责维护收件人(消息接收方)与其已注册的邮件类型(包含相对消息处理程序)之间的链接。 可以使用消息处理程序将任何对象注册为给定邮件类型的收件人,每当使用 IMessenger 实例发送该类型的消息时,都会调用该对象。 还可以通过特定的通信通道道(每个通道都由唯一令牌标识)发送消息,以便多个模块可以交换同一类型的消息,而不会造成冲突。 在没有令牌的情况下发送的消息使用默认共享通道。

有两种方法可以执行消息注册:通过 IRecipient<TMessage> 接口或使用充当消息处理程序的 MessageHandler<TRecipient, TMessage> 委托。 第一个允许你向 RegisterAll 扩展的单个调用注册所有处理程序,该扩展会自动注册所有声明的消息处理程序的收件人,而后者在需要更多灵活性或想要将简单的 lambda 表达式用作消息处理程序时非常有用。

WeakReferenceMessengerStrongReferenceMessenger 还公开一个 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));

假设此消息类型用于简单的消息应用程序,它显示具有当前记录用户的用户名和配置文件图像的标头、包含对话列表的面板,以及包含当前对话中消息的另一个面板(如果已选中)。 假设这三个部分分别受 HeaderViewModelConversationsListViewModelConversationViewModel 类型支持。 在此方案中,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>();

示例

  • 查看示例应用(适用于多个 UI 框架),以了解 MVVM 工具包的实际运行情况。
  • 还可以在单元测试中查找更多示例。