Посыльный

Интерфейс IMessenger — это контракт для типов, которые можно использовать для обмена сообщениями между различными объектами. Это может быть полезно, чтобы ослабить связанность между различными модулями приложения без необходимости хранить сильные ссылки на типы, на которые есть ссылки. Кроме того, можно отправлять сообщения в определенные каналы, однозначно идентифицироваться маркером и иметь разные посланники в разных разделах приложения. Набор средств MVVM предоставляет две готовые реализации: WeakReferenceMessenger и StrongReferenceMessenger: первая из них использует внутри слабые ссылки, обеспечивая автоматическое управление памятью для получателей, тогда как вторая использует сильные ссылки и требует от разработчиков вручную отменять подписку получателей, когда они больше не нужны (подробнее о том, как отменить регистрацию обработчиков сообщений, см. ниже), но взамен обеспечивает более высокую производительность и значительно меньший расход памяти.

API платформы:IMessenger, WeakReferenceMessenger, StrongReferenceMessengerIRecipient<TMessage>MessageHandler<TRecipient, TMessage>ObservableRecipientRequestMessage<T>, AsyncRequestMessage<T>, . CollectionRequestMessage<T>AsyncCollectionRequestMessage<T>

Принцип работы

Типы, реализующие IMessenger, отвечают за поддержание связей между получателями (получателями сообщений) и зарегистрированными для них типами сообщений, а также соответствующими обработчиками сообщений. Любой объект можно зарегистрировать в качестве получателя для заданного типа сообщения с помощью обработчика сообщений, который будет вызываться всякий раз IMessenger , когда экземпляр используется для отправки сообщения этого типа. Кроме того, можно отправлять сообщения через определенные каналы связи (каждый из которых определяется уникальным маркером), чтобы несколько модулей могли обмениваться сообщениями одного типа без возникновения конфликтов. Сообщения, отправленные без токена, отправляются через общий канал по умолчанию.

Существует два способа регистрации сообщений: через интерфейс или с помощью делегата, действующего IRecipient<TMessage>MessageHandler<TRecipient, TMessage> в качестве обработчика сообщений. Первый позволяет зарегистрировать все обработчики с одним вызовом RegisterAll расширения, который автоматически регистрирует получателей всех объявленных обработчиков сообщений, в то время как последний полезен, если требуется больше гибкости или когда вы хотите использовать простое лямбда-выражение в качестве обработчика сообщений.

И WeakReferenceMessenger, и StrongReferenceMessenger также предоставляют свойство Default, которое предлагает потокобезопасную реализацию, встроенную в пакет. При необходимости можно также создать несколько экземпляров messenger, например, если другой экземпляр внедряется с поставщиком услуг DI в другой модуль приложения (например, несколько окон, работающих в одном процессе).

Примечание.

Поскольку тип WeakReferenceMessenger проще в использовании и соответствует поведению типа Messenger из библиотеки MvvmLight, именно он по умолчанию используется типом ObservableRecipient в MVVM Toolkit. 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 заботится о доставке сообщений всем зарегистрированным получателям. Обратите внимание, что получатель может подписаться на сообщения определенного типа. Обратите внимание, что типы наследуемых сообщений не регистрируются в реализациях по умолчанию IMessenger , предоставляемых набором средств MVVM.

Если получатель больше не нужен, необходимо отменить регистрацию, чтобы он перестал получать сообщения. Вы можете отменить регистрацию по типу сообщения, маркеру регистрации или получателю:

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

Примеры

  • Ознакомьтесь с примером приложения (для нескольких платформ пользовательского интерфейса), чтобы просмотреть набор средств MVVM в действии.
  • Дополнительные примеры можно найти в модульных тестах.