疎結合コンポーネント間の通信
ヒント
この内容は電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』からの抜粋です。これは .NET Docs で閲覧することも、無料の PDF をダウンロードしてオフラインで読むこともできます。
発行-サブスクライブ パターンは、パブリッシャーが、サブスクライバーと呼ばれる受信者を知らないままメッセージを送信するメッセージング パターンです。 同様に、サブスクライバーは、パブリッシャーを知らないまま特定のメッセージをリッスンします。
.NET のイベントは、発行-サブスクライブ パターンを実装し、コントロールやそれを含むページなど、疎結合が不要な場合に、コンポーネント間の通信レイヤーに対して最もシンプルな方法です。 ただし、パブリッシャーとサブスクライバーの有効期間はオブジェクト参照によって相互に結合され、サブスクライバーの種類にはパブリッシャーの種類への参照が含まれている必要があります。 これにより、メモリ管理の問題が発生する可能性があります。静的または有効期間の長いオブジェクトのイベントをサブスクライブする、有効期間が短いオブジェクトがある場合は、特にそうです。 イベント ハンドラーが削除されていない場合、サブスクライバーは、パブリッシャー内のサブスクライバーへの参照によって保持されます。これにより、サブスクライバーのガベージ コレクションが妨げられるか、または遅延します。
MVVM Toolkit Messenger の概要
MVVM Toolkit IMessenger
は、発行-サブスクライブ パターンを記述するインターフェイスであり、オブジェクトと型の参照によってリンクしにくいコンポーネント間で、メッセージベースの通信を行うことができます。 このメカニズムにより、パブリッシャーとサブスクライバーは相互に直接参照することなく通信できるため、コンポーネント間の依存関係を減らすことができます。また、コンポーネントを個別に開発およびテストすることもできます。
注意
MVVM Toolkit Messenger は CommunityToolkit.Mvvm
パッケージの一部です。 プロジェクトにパッケージを追加する方法については、Microsoft デベロッパー センターの「MVVM Toolkit の概要」を参照してください。
警告
.NET MAUI には、使用が推奨されなくなった組み込み MessagingCenter
クラスが含まれています。 代わりに MVVM Toolkit Messenger を使用してください。
IMessenger
インターフェイスを使用すると、マルチキャストの発行-サブスクライブ機能を使用できます。 つまり、1 つのメッセージを発行する複数のパブリッシャーが存在することができ、同じメッセージをリッスンしている複数のサブスクライバーが存在することができます。 この関係を次の図に示します。
CommunityToolkit.Mvvm
パッケージに付属する IMessenger
インターフェイスの実装は 2 つあります。 WeakReferenceMessenger
は弱参照を使用するため、メッセージ サブスクライバーのクリーンアップが容易になる可能性があります。 これは、サブスクライバーに明確に定義されたライフサイクルがない場合に適したオプションです。 StrongReferenceMessenger
は強参照を使用するため、パフォーマンスが向上し、サブスクリプションの有効期間が明確に制御される可能性があります。 非常に限られた有効期間を持つワークフロー (たとえば、ページの OnAppearing
および OnDisappearing
メソッドにバインドされているサブスクリプション) があり、パフォーマンスが懸念される場合は、StrongReferenceManager
の方が優れたオプションになる可能性があります。 これらの実装はどちらも既定の実装で利用でき、WeakReferenceMessenger.Default
または StrongReferenceMessenger.Default
を参照することですぐに使用できます。
注意
IMessenger
インターフェイスでは疎結合クラス間の通信が許可されますが、この問題に対する唯一のアーキテクチャ ソリューションは提供されません。 たとえば、ビュー モデルとビューの間の通信は、バインド エンジンとプロパティ変更通知を介して行うこともできます。 さらに、移動中にデータを渡すことで、2 つのビュー モデル間の通信を実現することもできます。
eShop マルチプラットフォーム アプリでは、WeakReferenceMessenger
クラスを使用して疎結合コンポーネント間で通信します。 アプリで AddProductMessage
という名前の 1 つのメッセージを定義します。 アイテムが買い物かごに追加されると、CatalogViewModel
クラスによって AddProductMessage
が発行されます。 その応答として、CatalogView
クラスはメッセージをサブスクライブし、これを使用して、アニメーションで製品の追加を強調表示します。
eShop マルチプラットフォーム アプリでは、別のクラスで発生するアクションに応答して UI を更新するために WeakReferenceMessenger
が使用されます。 そのため、メッセージはクラスが実行されているスレッドから発行され、サブスクライバーは同じスレッドでメッセージを受信します。
ヒント
UI 更新を実行するときに、UI またはメイン スレッドに対してマーシャリングします。 このスレッドでユーザー インターフェイスを更新しないと、アプリケーションがクラッシュしたり不安定になったりする可能性があります。
UI を更新するためにバックグラウンド スレッドから送信されたメッセージが必要な場合は、MainThread.BeginInvokeOnMainThread
メソッドを呼び出してサブスクライバーの UI スレッドでメッセージを処理します。
Messenger
の詳細については、Microsoft デベロッパー センターの Messenger に関するページを参照してください。
メッセージの定義
IMessenger
のメッセージは、カスタム ペイロードを提供するカスタム オブジェクトです。 次のコード例は、eShop マルチプラットフォーム アプリ内で定義された AddProductMessage
メッセージを示しています。
public class AddProductMessage : ValueChangedMessage<int>
{
public AddProductMessage(int count) : base(count)
{
}
}
基底クラスは ValueChangedMessage<T>
を使用して定義されます。ここで T
は、データを渡すために必要な任意の型とすることができます。 メッセージのパブリッシャーとサブスクライバーの両方が、特定の種類のメッセージ (例: AddProductMessage
) を受け取ることができます。 これにより、双方がメッセージ コントラクトに同意し、そのコントラクトを使用して提供されるデータの一貫性を確保できます。 さらに、この方法を使用すると、コンパイル時のタイプ セーフとリファクタリングのサポートが提供されます。
メッセージの発行
メッセージを発行するには、IMessenger.Send
メソッドを使用する必要があります。 ほとんどの場合、これは WeakReferenceMessenger.Default.Send
または StrongReferenceMessenger.Default.Send
を使用してアクセスできます。 送信されるメッセージは任意のオブジェクトの種類とすることができます。 次のコード例は、AddProduct
メッセージの発行を示します。
WeakReferenceMessenger.Default.Send(new Messages.AddProductMessage(BadgeCount));
この例では、Send
メソッドは、ダウンストリーム サブスクライバーが受信する AddProductMessage
オブジェクトの新しいインスタンスを指定しています。 複数の異なるサブスクライバーが間違ったメッセージを受信せずに同じ種類のメッセージを受信する必要がある場合に使用するために、追加の 2 番目のトークン パラメーターを追加できます。
Send
メソッドでは、ファイア アンド フォーゲット アプローチを使用して、メッセージとそのペイロード データが発行されます。 そのため、メッセージを受信するために登録されたサブスクライバーがない場合でも、メッセージが送信されます。 この状態では、送信されたメッセージは無視されます。
メッセージのサブスクライブ
サブスクライバーは、IMessenger.Register<T>
オーバーロードのいずれかを使用して、メッセージを受信するように登録できます。 次のコード例は、eShop マルチプラットフォーム アプリが AddProductMessage
メッセージをサブスクライブし、処理する方法を示しています。
WeakReferenceMessenger.Default
.Register<CatalogView, Messages.AddProductMessage>(
this,
async (recipient, message) =>
{
await recipient.Dispatcher.DispatchAsync(
async () =>
{
await recipient.badge.ScaleTo(1.2);
await recipient.badge.ScaleTo(1.0);
});
});
この例では、Register
メソッドは AddProductMessage
メッセージをサブスクライブし、メッセージの受信に応答してコールバック デリゲートを実行します。 このコールバック デリゲートは、ラムダ式として指定され、UI を更新するコードを実行します。
注意
デリゲート内でそのオブジェクトを取得することを避けるには、コールバック デリゲート内で this
を使用しないようにします。 これはパフォーマンスの向上に役立ちます。 代わりに、recipient
パラメータを使用します。
ペイロード データが提供されている場合は、コールバック デリゲート内からペイロード データを変更しないでください。複数のスレッドが受信データに同時にアクセスする可能性があるためです。 このシナリオでは、コンカレンシー エラーを回避するためにペイロード データを変更不可にする必要があります。
メッセージのサブスクライブ解除
サブスクライバーは、受信する必要がなくなったメッセージのサブスクライブを解除することができます。 これは、次のコード例に示すように、IMessenger.Unregister
オーバーロードのいずれかで実現されます。
WeakReferenceMessenger.Default.Unregister<Messages.AddProductMessage>(this);
注意
この例では、WeakReferenceMessenger
で未使用のオブジェクトのガベージ コレクションができるため、Unregister
を呼び出す必要はありません。 StrongReferenceMessenger
を使用した場合は、使用されなくなったサブスクリプションに対して Unregister
を呼び出すことをお勧めします。
この例では、Unsubscribe
メソッドの構文で、メッセージの型引数と、メッセージをリッスンしている受信者オブジェクトを指定しています。
まとめ
MVVM Toolkit IMessenger
は、発行-サブスクライブ パターンを記述するインターフェイスであり、オブジェクトと型の参照によってリンクしにくいコンポーネント間で、メッセージベースの通信を行うことができます。 このメカニズムにより、パブリッシャーとサブスクライバーは相互に参照することなく通信できるため、コンポーネント間の依存関係を減らすことができます。また、コンポーネントを個別に開発およびテストすることもできます。
.NET