.NET では、コンストラクタから仮想メソッドを呼び出すと、派生型コンストラクタが処理されていない状態でオーバーライドメソッドが実行されてしまいます。
例として、基底クラス Base と 派生クラス Derived があるとします。
(括弧付き数字は実行される順番です)
// 基底クラス
class Base
{
private static bool baseStaticFieldInitialized = ConsoleOut("(1) 基底型静的フィールドの初期化");
private bool baseInstanceFieldInitialized = ConsoleOut("(4) 基底型インスタンスフィールドの初期化");
public Base()
{
ConsoleOut("(5) 基底型コンストラクタの実行");
// ※派生型コンストラクタはまだ実行されていないが、この時点でもインスタンスは派生型
Assert.AreEqual(typeof(Derived), this.GetType());
// ※オーバーライドされた派生型メソッドが実行される。
OverridableMethod();
}
// 仮想メソッド
protected virtual void OverridableMethod()
{
// ※インスタンス型でオーバーライドされている場合、コンストラクタからの呼び出しでも実行されない。
Assert.Fail();
}
protected static bool ConsoleOut(string step)
{
Console.WriteLine(step);
return true;
}
}
// 派生クラス
sealed class Derived : Base
{
private static bool derivedStaticFieldInitialized = ConsoleOut("(2) 派生型静的フィールドの初期化");
// ※基底型インスタンスフィールド、基底型コンストラクタより先に処理される。
private bool derivedInstanceFieldInitialized = ConsoleOut("(3) 派生型インスタンスフィールドの初期化");
private bool derivedConstructorCalled = false;
public Derived()
{
ConsoleOut("(7) 派生型コンストラクタの実行");
this.derivedConstructorCalled = true;
}
// オーバーライドメソッド
protected override void OverridableMethod()
{
ConsoleOut("(6) 派生型オーバーライドメソッドの実行");
// ※基底型コンストラクタから呼び出された場合、
// 派生型フィールドは初期化済みだが、派生型コンストラクタが未処理のまま実行されてしまう。
Assert.IsTrue(this.derivedInstanceFieldInitialized);
Assert.IsFalse(this.derivedConstructorCalled);
}
}
実行結果は以下のようになります。
new Derived();
(1) 基底型静的フィールドの初期化
(2) 派生型静的フィールドの初期化
(3) 派生型インスタンスフィールドの初期化
(4) 基底型インスタンスフィールドの初期化
(5) 基底型コンストラクタの実行
(6) 派生型オーバーライドメソッドの実行
(7) 派生型コンストラクタの実行
なお、非 sealed 型コンストラクタからの仮想メソッドを呼び出しは、コード分析 FxCop(弊社支援サービス参考)で、「CA2214: コンストラクターのオーバーライド可能なメソッドを呼び出しません。」として警告されます。