WeakEvent パターン

更新 : 2007 年 11 月

一般的なアプリケーションでは、イベント ソースにアタッチされているハンドラが、このハンドラをソースにアタッチしたリスナ オブジェクトとの関連によって、破棄されないことがあります。このような状況は、メモリ リークにつながる可能性があります。Windows Presentation Foundation (WPF) では、特定のデザイン パターンが導入されており、このデザイン パターンを使用して、特定のイベントの専用マネージャ クラスを提供し、そのイベントのリスナにインターフェイスを実装することによって、この問題に対処できます。このデザイン パターンは、WeakEvent パターンと呼ばれます。

WeakEvent パターンを実装する理由

イベントのリッスンによって、メモリ リークが発生する可能性があります。イベントをリッスンする場合の一般的な手法は、言語固有の構文を使用して、ソース上でイベントにハンドラをアタッチすることです。たとえば、C# では、その構文は source.SomeEvent += new SomeEventHandler(MyEventHandler) です。

この手法では、イベント ソースからイベント リスナへの強い参照を作成します。通常、リスナのイベント ハンドラをアタッチすると、リスナはオブジェクトの有効期間を持ちますが、この有効期間はソースのオブジェクトの有効期間に影響されます (イベント ハンドラが明示的に削除されない場合)。ただし、特定の状況では、リスナのオブジェクトの有効期間を、ソースの有効期間によってではなく、アプリケーションのビジュアル ツリーに現在属しているかどうかなどの別の要素によってのみ制御したい場合もあります。ソース オブジェクトの有効期間がリスナのオブジェクトの有効期間を超える場合、常に通常のイベント パターンによってメモリ リークが発生します。つまり、このリスナは想定した以上に長く維持されます。

WeakEvent パターンは、このメモリ リークの問題を解決するように設計されています。WeakEvent パターンは、リスナをイベントに登録する必要がある際に、いつ登録を解除するかを明示的には認識できない場合、およびソースのオブジェクトの有効期間が、リスナの "有用な" オブジェクトの有効期間を超える場合に使用できます ("有用な" という概念の定義は、ユーザーが決定します)。WeakEvent パターンを使用すると、リスナのオブジェクトの有効期間の特性にまったく影響を与えることなく、リスナをイベントに登録し、イベントを受け取ることができます。実際には、リスナがガベージ コレクションの対象であるかどうかを判断する際に、ソースからの暗黙的な参照は考慮されません。この参照は弱い参照であり、このことから WeakEvent パターンおよび関連する API の名前が付けられています。リスナはガベージ コレクトされ、そうでなければ破棄されて、ソースは存続することができ、破棄されたオブジェクトへの収集不能なハンドラ参照を継続することもありません。

だれが WeakEvent パターンを実装するのか

WeakEvent パターンの実装は、主にコントロール作成者にとって意味があります。それは、コントロールの作成者には、作成したコントロールの動作と格納およびそれを組み込むアプリケーションに与える影響に対する主な責任があるためです。これには、コントロール オブジェクトの有効期間の動作 (特に、上に示したメモリ リークの問題への対応) が含まれます。

WeakEvent パターンを適用すると本質的に役立つシナリオがいくつかあります。このようなシナリオの 1 つが、データ バインディングです。データ バインディングでは、データ ソースであるソース オブジェクトがリスナ オブジェクトにまったく依存しない場合が多く、これはバインディングのターゲットとなります。WPF データ バインディングの多くの側面は、イベントの実装方法に適用される WeakEvent パターンをあらかじめ含んでいます。

WeakEvent パターンを実装する方法

WeakEvent パターンの実装には、次の 3 つの側面があります。

  • マネージャを WeakEventManager クラスから派生させます。

  • IWeakEventListener インターフェイスを、ソースへの強い参照を生成せずに、弱いイベントのリスナを登録するすべてのクラスに実装します。

  • リスナの登録時に、リスナでパターンを使用する場合は、イベントの通常の追加アクセサと削除アクセサを使用しないでください。代わりに、そのイベントの専用 WeakEventManager での "AddListener" 実装と "RemoveListener" 実装を使用します。

WeakEventManager

通常、マネージャ クラスは、パターンを実装するイベントと 1 対 1 の関係で作成します。たとえば、1 つのイベント Spin がある場合、このイベント専用の弱いイベント マネージャとして 1 つの SpinEventManager クラスを派生させます。このイベントが複数のソースのクラスにあり、一般に各クラスで同様に動作し、イベント データ型を共有する場合は、それぞれ同じマネージャを使用することができます。

WeakEventManager クラスからの派生についての実装のチェックリストを構成するのは、2 つの仮想メソッドのオーバーライド、およびメンバ名を仮想テンプレートによって明確には制御されないにもかかわらず存在するいくつかの他のメンバの公開です。オーバーライドは、イベント配信モードを開始または終了するために、WPF インフラストラクチャによって使用されます。他のメンバは機能の提供に必要であり、これによって、独自の IWeakEventListener 実装は WeakEventManager を使用して、イベントにリスナをアタッチできます。

WeakEventManager からの派生に関する実装の詳細については、WeakEventManager のリファレンス トピックにある「継承時の注意」を参照してください。

IWeakEventListener

IWeakEventListener の実装クラスの役割は 1 つだけであり、それはインターフェイス メソッドの ReceiveWeakEvent の実装です。ReceiveWeakEvent の実装は、そのクラスに存在するすべてのイベントの参照を、適切な WeakEventManager に設定する一元的な実装である必要があります。

IWeakEventListener インターフェイスの実装に関する実装時の詳細については、ReceiveWeakEvent メソッドのリファレンス トピックにある「実装時の注意」を参照してください。

リスナのアタッチ

通常のイベントの ClockwiseSpin イベント (Spinner によって定義) があるとします。このイベントのパターンを使用するには、WeakEventManager から派生する既存の ClockwiseSpinEventManager クラスを使用するか、手動で実装するかのいずれかです。リスナとなる SpinListener のリスナ クラスがある場合、ハンドラをアタッチする通常の手法 (パターンを使用しない) は、次に示す += 構文の使用です。

spinnerInstance.ClockwiseSpin += new EventHandler(MyOnCWSpinHandler);

ただし、IWeakEventListener を実装し、その実装での ClockwiseSpin イベントとそのマネージャを記述するクラスがある場合、WeakEvent パターンの代わりに使用する構文は、次のとおりです。

ClockwiseSpinEventManager.AddListener(spinnerInstance, this);

このイベントの処理ロジックは、クラスでの ReceiveWeakEvent 実装の 1 つのケースにおいて指定されますが、通常のデリゲート ベースのハンドラとしてではありません。

外部イベントのパターンの実装

WeakEvent パターンの有意義な 1 つの特徴は、コード ベースの一部でないイベントに対してパターンを実装できることです。ソースの観点からは、ハンドラがそのイベントにアタッチされる方法には違いがなく、WeakEventManager によって制御されます。イベントの WeakEventManager を定義し、次に、パターンを使用してイベントをリッスンすると考えられるリスナの ReceiveWeakEvent ロジックの一部としてイベントを記述する必要があるだけです。

参照

概念

ルーティング イベントの概要

データ バインディングの概要

参照

WeakEventManager

IWeakEventListener