static フィールドの初期化のタイミング

.NET には beforefieldinit というフラグがあることをはじめて知った。
beforefieldinitフラグ - easy work, easy life

検索してみると、Insider.NET にも似たような話題が挙がっていた。
「開発環境とEXEとでstatic領域の初期化タイミングがちがう」(1) Insider.NET − @IT

これは知らなかった。僕もちょっと実験してみよう。

using System;

class Hoge
{
  // static フィールドとして Hoge オブジェクトを生成。
  // この Hoge オブジェクトが new されるタイミングが問題。
  static Hoge hoge = new Hoge();

  // ダミーの空の関数
  public static void Dummy() {}

  // コンストラクタで文字列を出力
  Hoge() { Console.WriteLine( "constructed." ); }

  // 静的コンストラクタ
  static Hoge() {}
}

static class Program
{
  static void Main()
  {
    Console.WriteLine( "start." );
    Hoge.Dummy();
    Console.WriteLine( "end." );
  }
}

これを実行するとこうなる。

>BeforeFieldInit.exe
start.
constructed.
end.

Hoge.Dummy() 関数にアクセスするときに静的フィールドが初期化されていることが見て取れる。
では次に、静的コンストラクタをコメントアウトしてみよう。

  ...
  // 静的コンストラクタ
  // static Hoge() {}
}
...

これを実行すると、こうなる。

>BeforeFieldInit.exe
constructed.
start.
end.

むむっ、確かに Hoge.Dummy() にアクセスするより前に静的フィールドの初期化が行われているようだ。これは一体どういうカラクリなのか?

上記のサイトには、次のURLが参考サイトとして挙げられている。
http://www.yoda.arachsys.com/csharp/beforefieldinit.html

The CLI specification (ECMA 335) states in section 8.9.5:

  1. A type may have a type-initializer method, or not.
  2. A type may be specified as having a relaxed semantic for its type-initializer method (for convenience below, we call this relaxed semantic BeforeFieldInit)
  3. If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type
  4. If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):
    • first access to any static or instance field of that type, or
    • first invocation of any static, instance or virtual method of that type

ちょっと訳してみると・・・

  1. 型は type-initializer を持ちうる
  2. 型は type-initializer メソッドに緩いセマンティックを指定し得る(以下では簡単のため、この緩いセマンティックを BeforeFieldInit と呼ぶことにする)
  3. BeforeFieldInitが指定されている場合には、その型に定義されている任意の静的フィールドに最初にアクセスしたとき、あるいはそれより前に type-initializer メソッドが実行される。
  4. BeforeFieldInitが指定されていない場合には、type-initializer は次のタイミングで実行される(次のタイミングがトリガーとなる)
    • その型の任意の静的フィールドおよびインスタンスフィールドに最初にアクセスしたとき
    • その型の任意の静的メソッド、インスタンスメソッド、仮想関数が最初に呼び出されたとき

つまり、BeforeFirstInitが指定されている場合には、静的フィールドに対する最初のアクセスよりもずっと前に初期化されることもあり得る、ということらしい。で、C# の仕様では静的コンストラクタがない場合には BeforeFirstInit が指定される、ということらしい。
これは知らないとハマりそうだ。心に留めておこう。