DependencyObject の安全なコンストラクタ パターン
更新 : 2007 年 11 月
通常、クラスのコンストラクタでは、仮想メソッドやデリゲートなどのコールバックは呼び出しません。コンストラクタは派生クラスのコンストラクタの基底の初期化として呼び出されることがあるためです。対象のオブジェクトの初期化が不完全な状態で仮想メソッドに入る可能性があります。ただし、プロパティ システム自体は依存関係プロパティ システムの一部としてコールバックを呼び出し、内部的に公開します。SetValue の呼び出しによって依存関係プロパティの値を設定するような簡単な演算には、決定のどこかにコールバックが潜在的に含まれます。この理由から、使用する型が基本クラスとして使われる場合に、コンストラクタの本文で依存関係プロパティの値を設定すると問題が起こる可能性があるため、注意する必要があります。DependencyObject コンストラクタを実装するときには、依存関係プロパティの状態および付随するコールバックに関する固有の問題を回避するための特定のパターンがあります。ここでは、そのパターンについて説明します。
このトピックには次のセクションが含まれています。
- プロパティ システムの仮想メソッド
- 安全なコンストラクタ パターン
- 関連トピック
プロパティ システムの仮想メソッド
依存関係プロパティの値を設定する SetValue の呼び出しの計算中には、ValidateValueCallback、PropertyChangedCallback、CoerceValueCallback、OnPropertyChanged の各仮想メソッドまたはコールバックが潜在的に呼び出されます。これらの仮想メソッドまたはコールバックは、Windows Presentation Foundation (WPF) のプロパティ システムと依存関係プロパティの汎用性を高めるうえで、それぞれ特定の目的を果たします。これらの仮想メソッドを使用してプロパティ値の決定をカスタマイズする方法については、「依存関係プロパティのコールバックと検証」を参照してください。
FXCop のルール強制とプロパティ システムの仮想メソッドの比較
ビルド プロセスの一部として Microsoft ツールの FXCop を使用している場合、基本コンストラクタを呼び出す特定の WPF フレームワーク クラスを派生させるとき、または派生クラスで独自の依存関係プロパティを実装するときに、FXCop の特定のルール違反が発生することがあります。この違反に対する名前文字列は、次のとおりです。
DoNotCallOverridableMethodsInConstructors
このルールは、FXCop に設定されている既定のパブリック ルールの一部です。このルールによって報告されるのは、依存関係プロパティ システムの仮想メソッドを最終的に呼び出す、依存関係プロパティ システム内のトレースです。このルール違反は、このトピックで説明されている推奨コンストラクタ パターンに従っている場合も表示され続けるため、FXCop のルール セット設定でこのルールを無効にするか抑制する必要があります。
既存のクラスの使用ではなくクラスの派生が大半の原因
このルールによって報告される問題は、コンストラクタで仮想メソッドを呼び出すように実装したクラスが派生される場合に発生します。クラスをシールする場合、クラスが派生されないとわかっている場合、またはクラスが派生されないように強制する場合は、ここで説明する内容や FXCop のルールによって起こる問題は該当しません。ただし、テンプレートや拡張可能なコントロール ライブラリのセットを作成する場合などのように、基本クラスとして使用されることを想定したクラスを作成する場合は、ここで説明されているコンストラクタの推奨パターンに従う必要があります。
既定のコンストラクタにおいて、コールバックによって要求されるすべての値の初期化の必要性
任意のクラスのオーバーライドまたはコールバック (「プロパティ システムの仮想メソッド」の一覧のコールバック) によって使用されるすべてのインスタンス メンバは、クラスの既定のコンストラクタで初期化する必要があります。これは、既定以外のコンストラクタのパラメータによって値の一部に "実際の" 値が入る場合も同様です。
次のコード例 (および以降の例) は、この規則に違反する擬似 C# コードの例であり、問題を説明しています。
public class MyClass : DependencyObject
{
public MyClass() {}
public MyClass(object toSetWobble)
: this()
{
Wobble = toSetWobble; //this is backed by a DependencyProperty
_myList = new ArrayList(); // this line should be in the default ctor
}
public static readonly DependencyProperty WobbleProperty =
DependencyProperty.Register("Wobble", typeof(object), typeof(MyClass));
public object Wobble
{
get { return GetValue(WobbleProperty); }
set { SetValue(WobbleProperty, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
int count = _myList.Count; // null-reference exception
}
private ArrayList _myList;
}
アプリケーション コードによって new MyClass(objectvalue) が呼び出されると、既定のコンストラクタと基本クラスのコンストラクタが呼び出されます。次に、Property1 = object1 が設定されると、対応する MyClassDependencyObject の仮想メソッドの OnPropertyChanged が呼び出されます。オーバーライドは、まだ初期化されていない _myList を参照します。
これらの問題を回避する方法の 1 つは、コールバックが他の依存関係プロパティだけを使用し、それぞれの使用する依存関係プロパティが、登録済みのメタデータの一部として確立された既定値を持つようにすることです。
安全なコンストラクタ パターン
クラスが基本クラスとして使用される場合の不完全な初期化のリスクを回避するには、次のパターンに従います。
基本の初期化を呼び出す既定のコンストラクタ
次の例では、これらのコンストラクタを実装して、基本の初期化を呼び出します。
public MyClass : SomeBaseClass {
public MyClass() : base() {
// ALL class initialization, including initial defaults for
// possible values that other ctors specify or that callbacks need.
}
}
既定外の (簡易) コンストラクタ (すべての基本シグネチャと一致しない場合)
これらのコンストラクタがパラメータを使用して初期化の依存関係プロパティを設定する場合は、最初に初期化のための独自のクラスの既定のコンストラクタを呼び出し、次にパラメータを使用して依存関係プロパティを設定します。この場合、クラスによって定義された依存関係プロパティ、または基本クラスから継承された依存関係プロパティのどちらかを使用できますが、いずれの場合も次のパターンを使用します。
public MyClass : SomeBaseClass {
public MyClass(object toSetProperty1) : this() {
// Class initialization NOT done by default.
// Then, set properties to values as passed in ctor parameters.
Property1 = toSetProperty1;
}
}
既定外の (簡易) コンストラクタ (基本シグネチャと一致しない場合)
同じパラメータ化を使用して基本コンストラクタを呼び出す代わりに、独自のクラスの既定のコンストラクタを再び呼び出します。基本初期化子を呼び出さないでください。代わりに、this() を呼び出す必要があります。次に、渡されたパラメータを関連するプロパティを設定する値として使用し、元のコンストラクタの動作を複製します。特定のパラメータを設定するプロパティを決定する場合は、元の基本コンストラクタのドキュメントを参考に使用します。
public MyClass : SomeBaseClass {
public MyClass(object toSetProperty1) : this() {
// Class initialization NOT done by default.
// Then, set properties to values as passed in ctor parameters.
Property1 = toSetProperty1;
}
}
すべてのシグネチャを一致させる必要性
基本型が複数のシグネチャを持つ場合は、すべての可能なシグネチャを、さらにプロパティを設定する前に、クラスの既定のコンストラクタを呼び出す推奨パターンを使用する独自のコンストラクタの実装に意図的に一致させる必要があります。
SetValue による依存関係プロパティの設定
プロパティの設定の利便性のためにラッパーを持たないプロパティを設定し、SetValue を使用して値を設定する場合は、同じパターンが適用されます。コンストラクタ パラメータを介して渡される SetValue の呼び出しもまた、初期化のためのクラスの既定のコンストラクタを呼び出します。