タイプイニシャライザとコンストラクタ
フィールド値を初期化するには、宣言時に一緒に行うか、コンストラクタ内で行うかのどちらかが 一般的だと思います。では、両者に何らかの違いはあるのでしょうか?ここでは型の初期化である タイプイニシャライザとインスタンスの初期化であるコンストラクタについてもみてみます。
宣言時とコンストラクタでは初期化のタイミングが違う
まずは、宣言時に初期化する方法と、コンストラクタ内で初期化する方法の違いを説明する為に 以下のようなコードを考えてみます。
public class Class1
{
public int i = 0;
}
public class Class1
{
public int i;
public Class1()
{
i = 0;
}
}
この違いを見るためにはILレヴェルでこのコードを検証する必要があるでしょう。以下のコードがその結果です。
そして、比較の為に何も書かれていないクラスの.ctorも載せておきます。
- ldarg.0 :第0番目の引数をスタックに読み込む
- ldc.i4.0 :int32型の定数(=0)をスタックに読み込む
- stfld :指定されたフィールドに値をストアする。
- call :指定されたメソッドを呼び出す。
ここまでくるともう一目瞭然と言った感じですが、上記両者の違いはその初期化のタイミングにあります。 上記のILを見る限り、C#コンパイラは宣言時の初期化式をまず.ctor(インスタンスの初期化じにCLRによって呼び出されるメソッド)に書き込んで、 次にコンストラクタの内部を記述しているということがわかります。 つまり、宣言時の初期化とコンストラクタ内での初期化を混在させた場合、 宣言時の初期化がまず行われ、次にコンストラクタ内での初期化が行われるということです。
staticなコンストラクタは通常のコンストラクタとは違う
では、次のコードはどうでしょうか?
{
public static int i = 0;
static Class1()
{
i = 1;
}
public Class1()
{
i = 2;
}
}
通常のコンストラクタと一緒にstaticのついたコンストラクタが混在した場合です。最終的(インスタンスを生成した後) にiの値はいくつに初期化されるのでしょうか?結論から言うと”2”に初期化されることになります。 では、インスタンスを生成する前だといくつでしょうか?答えは”1”です。このようにstaticなコンストラクタは通常の コンストラクタとはその性質が違います。ここで、このstaticなコンストラクタについて説明しておきます。
タイプイニシャライザは”型”の初期化をする
staticなコンストラクタはCLIにおいてタイプイニシャライザという名前が付けられており、コンストラクタにstaticを付加することによって これを表現しているのはC#の実装です。両者の最大の違いはそれが実行されるタイミングにあります。 タイプイニシャライザは「”型の”初期化にCLRによって呼び出され」、 コンストラクタは「”インスタンス”が確保されたときにCLRによってよびだされます」 その他、タイプイニシャライザは引数をとらないことや、常にprivateであることなど、 コンストラクタと違う点がありますが、最大の特徴は実行タイミングの違いであって、それが重要なことでしょう。
従って、これらのことを踏まえて上記の例を見てみると、何故iが1に初期化され2に初期化されるのかが納得いくはずです。 ただ、”型の初期化時”といわれてもインスタンスが確保される前だろうと予想はできますが、一体いつなのかは さだかではありません。これらについてはタイプイニシャライザとデッドロックをご覧下さい。