静的コンストラクタはインスタンスコンストラクタと違い、コンパイル時に特殊な処理が行われる。
静的コンストラクタを明示的に宣言しない場合、コンパイラが静的コンストラクタを用意してくれる。しかし、それだけではなく、beforefieldinitというフラグがそのクラスのMSIL定義に追加される。
静的コンストラクタを明示的に宣言した場合は、beforefieldinit フラグは追加されない。
beforefieldinit フラグがない(明示的に静的コンストラクタを宣言した)場合、その型の静的フィールドに初めてアクセスしようとした時、静的コンストラクタが実行される。また、静的コンストラクタが実行されたかどうかを確認するためのチェックが加えられる。
beforefieldinit フラグがある(明示的に静的コンストラクタを宣言しない)場合、その型の静的フィールドに初めてアクセスするよりも前に、静的コンストラクタが実行されることが保証される。beforefieldinitフラグがあると、ほとんどの場合、静的コンストラクタが実行されたかどうかを確認するチェックは加えられない。
静的コンストラクタが実行されたかどうかのチェックが加えられると、パフォーマンスが低下する場合があるらしい。
よって、静的フィールドの初期化は宣言時に行い、静的コンストラクタは明示的に宣言しないようにするのが適切である。
[ 検証 ]
以下のような検証用プログラムを組んでみた。
検証用プログラムclass Program
{
static void Main(string[] args)
{
double time1 = Hoge.InitTime.Subtract(DateTime.Now).TotalSeconds;
double time2 = Foo.InitTime.Subtract(DateTime.Now).TotalSeconds;
Console.WriteLine("beforefieldinitフラグありの場合の初期化:{0}", time1);
Console.WriteLine("beforefieldinitフラグなしの場合の初期化:{0}", time2);
Console.ReadLine();
}
static class Hoge
{
public static DateTime InitTime = DateTime.Now;
}
static class Foo
{
public static DateTime InitTime;
static Foo()
{
Foo.InitTime = DateTime.Now;
}
}
}
検証用プログラムをコンパイルすると、HogeクラスとFooクラスのMSILコードは、以下のようになる。Hogeクラスのクラス宣言部にはbeforefieldinit フラグがあり、Fooクラスのクラス宣言部にはbeforefieldinit フラグがないことが確認できる。
HogeクラスのMSILコード.class nested private abstract auto ansi sealed beforefieldinit Hoge
extends object
{
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{
.maxstack 8
L_0000: call [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
L_0005: stsfld [mscorlib]System.DateTime ConsoleApplication1.Program/Hoge::InitTime
L_000a: ret
}
.field public static [mscorlib]System.DateTime InitTime
}
FooクラスのMSILコード.class nested private abstract auto ansi sealed Foo
extends object
{
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{
.maxstack 8
L_0000: nop
L_0001: call [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
L_0006: stsfld [mscorlib]System.DateTime ConsoleApplication1.Program/Foo::InitTime
L_000b: ret
}
.field public static [mscorlib]System.DateTime InitTime
}
検証用プログラムを実行すると、以下のような出力を得られる。
実行結果beforefieldinitフラグありの場合の初期化:-0.03125
beforefieldinitフラグなしの場合の初期化:0
この結果から、beforefieldinitフラグありの場合、静的フィールドにアクセスするよりも前に、静的フィールドの初期化が行われていることがわかる。
[ 参照 ]
値型の静的フィールドをインラインで初期化します
参照型の静的フィールドをインラインで初期化します
拡張させることのないメンバにはvirtualキーワードを付加しないこと。
シールすることによりコンパイラが最適化してくれる。また、他者に自分の意図しない拡張をされることも防げる。(場合によっては防げないけど・・・w)
拡張させることがわかってるクラスや、拡張させる可能性が充分あるクラスは当然シールするわけにはいかないが、それ以外のクラスって大概はシールしちゃっていいと思う。
「あとで機能追加させたい時に困る」なんてのはおかしい。そのクラス自体に手を入れれば済んだりするし(つーか、それで済むならそうすべき)、本当にそのクラスを継承して拡張させたい場合もsealedキーワード取り除いてメンバを適切にvirtualにすれば済むだけの話だし。
個人的には、クラス作成時にデフォルトでsealedキーワードが書き込まれてて欲しいな。