メッセンジャー

IMessenger インターフェイスは、異なるオブジェクト間でメッセージを交換するために使用できる型のコントラクトです。 これは、参照される型への強い参照を保持する必要なく、アプリケーションのさまざまなモジュールを分離するのに役立ちます。 また、トークンによって一意に識別される特定のチャネルにメッセージを送信したり、アプリケーションの異なるセクションに異なるメッセンジャーを含めたりすることもできます。 MVVM ツールキットには、すぐに使える 2 つの実装 (WeakReferenceMessengerStrongReferenceMessenger) が用意されています。前者は内部で弱い参照を使用し、受信者に自動メモリ管理を提供します。一方、後者は強い参照を使用し、受信者が不要になったときに開発者が手動で受信者のサブスクライブを解除する必要がありますが (メッセージ ハンドラーの登録を解除する方法の詳細については、以下を参照してください)、その代わりに、パフォーマンスが向上し、メモリ使用量が大幅に削減されます。

プラットフォーム API:IMessengerWeakReferenceMessengerStrongReferenceMessengerIRecipient<TMessage>MessageHandler<TRecipient, TMessage>ObservableRecipientRequestMessage<T>AsyncRequestMessage<T>CollectionRequestMessage<T>AsyncCollectionRequestMessage<T>

しくみ

IMessenger を実装する型は、相対的なメッセージ ハンドラーを使用して、受信者 (メッセージの受信者) と登録されたメッセージの種類の間のリンクを保持する責任があります。 メッセージ ハンドラーを使用すると、任意のオブジェクトを特定のメッセージの種類の受信者として登録できます。メッセージ ハンドラーは、その種類のメッセージを送信するために IMessenger インスタンスが使用されるたびに呼び出されます。 また、特定の通信チャネル (それぞれが固有のトークンで識別される) を介してメッセージを送信することもできるため、複数のモジュールで競合を引き起こすことなく同じ種類のメッセージを交換できます。 トークンなしで送信されるメッセージは、既定の共有チャネルを使用します。

メッセージ登録を実行するには、IRecipient<TMessage> インターフェイスを使用する方法と、メッセージ ハンドラーとして機能する MessageHandler<TRecipient, TMessage> デリゲートを使用する方法の 2 つがあります。 前者の方法では、RegisterAll 拡張機能を 1 回呼び出すだけですべてのハンドラーを登録でき、宣言されたすべてのメッセージ ハンドラーの受信者が自動的に登録されます。一方、後者の方法は、より高い柔軟性が必要な場合、または単純なラムダ式をメッセージ ハンドラーとして使用する場合に役立ちます。

WeakReferenceMessengerStrongReferenceMessenger は両方とも、パッケージに組み込まれたスレッドセーフな実装を提供する Default プロパティも公開します。 必要に応じて複数のメッセンジャー インスタンスを作成することもできます。たとえば、DI サービス プロバイダーを使用して別のメッセンジャー インスタンスをアプリの別のモジュール (たとえば、同じプロセスで実行されている複数のウィンドウ) に挿入する場合などです。

Note

WeakReferenceMessenger 型の方が使いやすく、MvvmLight ライブラリのメッセンジャー型の動作と一致するため、MVVM Toolki の 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));

このメッセージの種類が単純なメッセージング アプリケーションで使用されているとしましょう。このアプリケーションでは、現在ログイン ユーザーのユーザー名とプロフィール画像を含むヘッダー、会話の一覧を含むパネル、現在の会話のメッセージを含む別のパネル (選択されている場合) が表示されます。 これら 3 つのセクションはそれぞれ、HeaderViewModelConversationsListViewModelConversationViewModel 型でサポートされているとします。 このシナリオでは、ログイン操作の完了後に HeaderViewModel によって LoggedInUserChangedMessage メッセージが送信される可能性があり、他の両方の 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 型を使用する場合、受信者を追跡するために弱い参照が使用されるため、これは厳密には必要ありません。つまり、未使用の受信者は、メッセージ ハンドラーがまだアクティブであっても、ガベージ コレクションの対象となります。 ただし、パフォーマンスを向上するために、サブスクリプションを解除することをお勧めします。 一方、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));

要求メッセージの使用

メッセンジャー インスタンスのもう 1 つの便利な機能は、あるモジュールから別のモジュールに値を要求するためにも使用できることです。 これを行うために、パッケージには次のように使用できる基底クラス 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 Toolkit の実際の動作を確認するには、サンプル アプリ (複数の UI フレームワーク向け) を参照してください。
  • また、単体テストでは、その他の例を確認できます。