見出し画像

[.NET] コンストラクタより先にメソッドが実行されてしまうからくり――を解き明かすために派生クラスのインスタンス生成過程を追跡する

.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: コンストラクターのオーバーライド可能なメソッドを呼び出しません。」として警告されます。

この記事が気に入ったらサポートをしてみませんか?