在本节中,Richter提到类型构造器的两种语义:precise和before-field-init。对C#编译器而言,如果发现有显式的类型构造器,它就看成precise语义;反之,则当成before-field-init对待。
{
public static Int32 s_x;
static Precise() { s_x = 123; } // precise语义,这是显式的类型构造器
}
IL代码如下:
extends [mscorlib]System.Object
{
.field public static int32 s_x
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: stsfld int32 Precise::s_x
IL_0008: ret
} // end of method Precise::.cctor
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Precise::.ctor
} // end of class Precise
而
internal sealed class BeforeFieldInit
{
public static Int32 s_x = 123; // before-field-init语义,这里仅仅是一个静态字段,
// C#编译器会在IL中构造一个类型构造器,但对这个类会多加上一个修饰以关键字beforefieldinit表示
}
IL代码如下:
extends [mscorlib]System.Object
{
.field public static int32 s_x
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method BeforeFieldInit::.ctor
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldc.i4.s 123
IL_0002: stsfld int32 BeforeFieldInit::s_x
IL_0007: ret
} // end of method BeforeFieldInit::.cctor
} // end of class BeforeFieldInit
类型构造器的调用时机是在第一次存取静态成员或第一次创建实例之前,总之是确保在存取静态成员或创建实例的时候类型是初始化已经完成。因此,如果在静态构造器中抛出异常,则在创建该类型的实例时会catch异常TypeInitializationException。 如下面的例子:
{
public static int m_y;
static TestClass1()
{
throw new Exception( "Exception from cctor" );
Console.WriteLine( "TestClass1::cctor" );
}
public Int32 m_x;
}
// 使用TestClass1
TestClass1 test =null;
try
{
test = new TestClass1();
}
catch ( TypeInitializationException ex )
{
Console.WriteLine( ex.ToString() );
}
// If type is not initialized successfully, the object is null
test.m_x = 1;
before-field-init语义其实是一种“松散(Relaxed)”语义,就是说CLR可以在先于存取静态成员的任一时候得到调用。而precise语义则是在刚好调用存取静态成员之前使静态构造函数得到调用。但有一点是一致的:用户不能显式调用静态构造函数,因为从IL代码可能以看出,静态构造函数是私有的,这就阻止了用户的显式调用。
before-field-init语义的性能好是因为在你准备使用某个类之前的某个时候,JIT已经编译过了;而precise语义则是相对在使用某个类之前的某个很短的时间内调用。这样看来书上的例子也验证了before-field-init的静态构造函数的调用要“快”于precise语义。我写了一个测试程序:
{
}
.assembly test
{
}
.module test.exe
// MVID: {E0B4B628-AEE0-4456-8EFB-14A10EEBCBE0}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02F50000
// =============== CLASS MEMBERS DECLARATION ===================
.class private auto ansi sealed beforefieldinit BeforeFieldInit
extends [mscorlib]System.Object
{
.field public static int32 s_x
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method BeforeFieldInit::.ctor
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
ldstr "BeforeFieldInit::cctor"
call void [mscorlib]System.Console::WriteLine(string)
IL_0000: ldc.i4.s 123
IL_0002: stsfld int32 BeforeFieldInit::s_x
IL_0007: ret
} // end of method BeforeFieldInit::.cctor
} // end of class BeforeFieldInit
.class private auto ansi sealed Precise
extends [mscorlib]System.Object
{
.field public static int32 s_x
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 9 (0x9)
.maxstack 8
ldstr "Precise::cctor"
call void [mscorlib]System.Console::WriteLine(string)
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: stsfld int32 Precise::s_x
IL_0008: ret
} // end of method Precise::.cctor
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Precise::.ctor
} // end of class Precise
.class public auto ansi sealed beforefieldinit Program
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 20 (0x14)
.maxstack 1
.locals init (class Precise V_0,
class BeforeFieldInit V_1)
IL_0000: nop
IL_0001: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_0006: pop
IL_0007: newobj instance void Precise::.ctor()
IL_000c: stloc.0
IL_000d: newobj instance void BeforeFieldInit::.ctor()
IL_0012: stloc.1
IL_0013: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Program::.ctor
} // end of class Program
用ILASM test.il编译
运行编译后的test.exe
程序首先进入System.Console::ReadKey的调用,此时我们还没有操作BeforeFieldInit或Precise的成员,但你已经发现BeforeFieldInit::cctor已经得到调用了。而只有在我们键入键程序继续运行后,Precise::cctor才得以调用。
Before-field-init的另一点需要注意的是多个Before-field-init语义的构造函数存在时,CLR不保证它们调用的先后顺序。因此如果这些静态构造函数内部实现有关联的话,它可能不会按照你声明的顺序调用。因此最后我们不要写类似的代码。