首先让我们来看两个类型的定义:
// 代码来自C# in Depth
// 为了后继的讨论方便,对类名进行了更改(Test改为Test1和Test2)
// —— Mitchell Chu
class Test1
{
static object o = new object();
}
class Test2
{
static object o;
static Test2()
{
o = new object();
}
}
这两个类,经常会被误认为是一样的,但实际情况是如何呢?让我们编译后,反编译后来看看他们产生的代码:
// IL代码
// Test1和Test2在定义的时候出现了不同.
// —— Mitchell Chu
.class private auto ansi beforefieldinit ClassLibrary1.Test1
extends [mscorlib]System.Object
{
// Fields
.field private static object o
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x206b
// 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 Test1::.ctor
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
// Method begins at RVA 0x205f
// Code size 11 (0xb)
.maxstack 8
IL_0000: newobj instance void [mscorlib]System.Object::.ctor()
IL_0005: stsfld object ClassLibrary1.Test1::o
IL_000a: ret
} // end of method Test1::.cctor
} // end of class ClassLibrary1.Test1
.class private auto ansi ClassLibrary1.Test2
extends [mscorlib]System.Object
{
// Fields
.field private static object o
// Methods
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
// Method begins at RVA 0x2073
// Code size 11 (0xb)
.maxstack 8
IL_0000: newobj instance void [mscorlib]System.Object::.ctor()
IL_0005: stsfld object ClassLibrary1.Test2::o
IL_000a: ret
} // end of method Test2::.cctor
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x207f
// 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 Test2::.ctor
} // end of class ClassLibrary1.Test2
比较后发现,大体一致,但Test1多了一个beforefieldinit标志。那beforefieldinit这个标志是做什么用的?
BeforeFieldInit是什么
MSDN上的表述是:
- BeforeFieldInit:指定调用此类型的静态方法并不强制系统初始化此类型。
然而,这并不能为我们释惑。
我们再参考下C#规范(MSDN上关于静态类也有类似描述)1:
类的静态构造函数在给定的程序域中最多只执行一次。静态构造函数的执行是通过程序域中的下列事件中第一个出现的事件来触发的:
和CLI规范(ECMA 335)第8.9.5节有如下表述1*:
- 类型可以有一个类型初始器方法(type-initializer method),或没有;
- 类型可为其类型初始器方法指定一个松语义(为了后继方便,我们称此松语义为BeforeFieldInit)
- 若标记为BeforeFieldInit,则类型初始器方法将在或某些时候先于第一次访问此类中定义的任一静态字段的时候执行;
- 如果没有标记为BeforeFieldInit,则类型的初始器方法将在下列情况下执行(即被触发):
- 第一次访问类中的任意静态或实例字段
- 第一次调用任意类的静态,实例或虚方法
从CLI规范中我们明白,BeforeFieldInit是一种类中成员初始化的方式,用来标明初始化时机。同时,还有一种没有使用BeforeFieldInit的方式,这种方式我们称之为—— precise方式3。
简单来说:
- BeforeFieldInit方式,我们不知道
.acctor
在什么时候运行,一切都将交由CLR做处理;
- Precise方式,我们可以明确的了解到
.acctor
的执行时间点。
如何定义BeforeFieldInit和Precise
如果我们在类中提供了静态的构造函数,那么,此类将自动变为Precise方式,这也就是为什么类的静态构造函数和CLI规范中未标记BeforeFieldInit的说明一致的原因。
而如果我们在类的定义中没有提供静态构造函数,.NET将会自动为我们提供一个静态构造函数(见后面静态VS实例构造函数),并将此类标记为BeforeFieldInit。
使用BeforeFieldInit或Precise
BeforeFieldInit将自身的初始化任务交给了CLR来处理,CLR可以自行决定在合适的时机来调用类的静态构造函数进行对静态成员的初始化,这样的一个好处就是CLR可以对其进行更好的优化时机,来提高程序的使用效能。当然,这也有一些小细节需要注意,当多个含有BeforeFieldInit的类同时存在时,正如我们无法了解和控制初始化时机一样,我们同样无法控制和了解到这些类的初始化顺序——一切都在CLR的掌控中,而不是我们。这在需要有序的情况下,就会出故障了,此时,我们需要考虑的是使用Precise方式。
Precise方式初始化时间点是明确的,因此我们可以利用这点,尽量减少对资源的浪费3—— 比如,我们常用的单例模式。
此处应有例子~~~
// BeforeFieldInit 与 Precise的性能比较
// Blog:blog.useasp.net
// Mitchell Chu
class Precise
{
public static int i = 1;
static Precise()
{
}
}
class BeforeFieldInit
{
public static int i = 1;
}
class Test
{
static void Main(string[] args)
{
// 测试100,000,000次
int times = 100000000;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
BeforeFieldInit.i = i;
}
sw.Stop();
Console.WriteLine("BeforeFieldInit take time:{0}ms", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < times; i++)
{
Precise.i = i;
}
sw.Stop();
Console.WriteLine("Precise take time:{0}ms", sw.ElapsedMilliseconds);
}
}
得出结果:
BeforeFieldInit take time:204ms
Precise take time:316ms
行文至此,应该是对BeforeFieldInit有所有了解了,那么让我们再看看另外一个例子,这个和哪个类一样?:
// 来自C# in Depth
// 这个例子和开篇两个类中的那个类是一样的呢?
class Test
{
static object o = new object();
static Test()
{
}
}
静态构造函数 VS 实例构造函数
.NET中类的初始化是在类的构造函数中进行的,因此一般类都有构造函数(也叫构造器,Constructor)。
在.NET中,对于没有构造函数的类,一般会自动为其分配一个默认的构造函数,如:对于含有静态成员的类,在没有使用静态构造函数的情况下,.NET会自动为其分配一个静态构造函数(.acctor
,需要有字段并默认给定初始化值的情况),此构造函数与实例构造函数(.ctor
)异同在于:
- 两者不存在的时候,.NET均会自动为其生成;
.ctor
是可编程访问的(在没有定义时,实例化会自动调用默认的),.accotr
则无法直接访问;
- 静态的构造函数(
.acctor
)提供对静态字段进行初始化,而实例构造函数(.ctor
)可以对静态和实例字段进行初始化。
CLI 规范第六版关于BeforeFieldInit的描述:
The semantics of when and what triggers execution of such type initialization methods, is as
follows:
1. A type can have a type-initializer method, or not.
2. A type can 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):
a. first access to any static field of that type, or
b. first invocation of any static method of that type, or
c. first invocation of any instance or virtual method of that type if it is a value
type or
d. first invocation of any constructor for that type.
5. Execution of any type's initializer method will not trigger automatic execution of
any initializer methods defined by its base type, nor of any interfaces that the type
implements.
[Note: BeforeFieldInit behavior is intended for initialization code with no interesting sideeffects,
where exact timing does not matter. Also, under BeforeFieldInit semantics, type
initializers are allowed to be executed at or before first access to any static field of that type, at
the discretion of the CLI.
参考:
- C# and beforefieldinit
- 静态构造函数(C#编程指南)
- JIT compiler and type constructors(.acctors)