其实,检查C#中实现静态变量编译过程非常容易。
C#被设计为编译为CIL(公共中间语言)。支持静态变量的C++也可以编译为CIL。
让我们看看它是如何实现的。首先,考虑下面这个简单的类:
public ref class Class1
{
private:
static int i = 0;
public:
int M() {
static int i = 0;
i++;
return i;
}
int M2() {
i++;
return i;
}
};
两种方法,行为相同 - i
初始化为0,在每次调用方法时递增并返回。让我们比较一下中间语言(IL)。
.method public hidebysig instance int32 M() cil managed
{
// Code size 20 (0x14)
.maxstack 2
.locals ([0] int32 V_0)
IL_0000: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_000c: ldsfld int32 '?i@?1??M@Class1@CppClassLibrary@@Q$AAMHXZ@4HA'
IL_0011: stloc.0
IL_0012: ldloc.0
IL_0013: ret
} // end of method Class1::M
.method public hidebysig instance int32 M2() cil managed
{
// Code size 20 (0x14)
.maxstack 2
.locals ([0] int32 V_0)
IL_0000: ldsfld int32 CppClassLibrary.Class1::i
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stsfld int32 CppClassLibrary.Class1::i
IL_000c: ldsfld int32 CppClassLibrary.Class1::i
IL_0011: stloc.0
IL_0012: ldloc.0
IL_0013: ret
} // end of method Class1::M2
这个和之前的是一样的,唯一的区别在于字段名称。它使用CIL中合法但在C++中非法的字符,因此相同的名称不能在C++代码中使用。C#编译器经常使用这种技巧来生成自动化的字段。唯一的区别在于静态变量无法通过反射访问 - 我不知道如何做到。
让我们看一个更有趣的例子。
int M3(int a) {
static int i = a;
i++;
return i;
}
现在开始有趣的部分。静态变量不能再在编译时初始化,而是必须在运行时进行初始化。编译器必须确保它只被初始化一次,因此必须是线程安全的。
生成的CIL代码为:
.method public hidebysig instance int32 M3(int32 a) cil managed
{
.maxstack 2
.locals ([0] int32 V_0)
IL_0000: ldsflda int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0005: call void _Init_thread_header_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_000a: ldsfld int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_000f: ldc.i4.m1
IL_0010: bne.un.s IL_0035
.try
{
IL_0012: ldarg.1
IL_0013: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0018: leave.s IL_002b
}
fault
{
IL_001a: ldftn void _Init_thread_abort_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_0020: ldsflda int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0025: call void ___CxxCallUnwindDtor(method void *(void*),
void*)
IL_002a: endfinally
}
IL_002b: ldsflda int32 '?$TSS0@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0030: call void _Init_thread_footer_m(int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)*)
IL_0035: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_003a: ldc.i4.1
IL_003b: add
IL_003c: stsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0041: ldsfld int32 '?i@?1??M3@Class1@CppClassLibrary@@Q$AAMHH@Z@4HA'
IL_0046: stloc.0
IL_0047: ldloc.0
IL_0048: ret
}
看起来更加复杂了。第二个静态字段,还有一些看起来像是关键部分(虽然我找不到关于_Init_thread_*方法的任何信息)。
它看起来并不那么简单了。性能也受到影响。在我看来,不实现C#中的静态变量是一个明智的决定。
总结一下,
为了支持静态变量,C#编译器需要:
- 为变量创建一个私有静态字段,确保名称是唯一的,并且不能直接在C#代码中使用。
- 通过反射使此字段不可见。
- 如果无法在编译时进行初始化,则使其线程安全。
这似乎并不多,但如果将几个类似于此类的功能组合在一起,复杂性就会呈指数级增长。
而你得到的唯一回报就是一个由编译器提供的易于使用、线程安全的初始化。
仅仅因为其他语言支持某个功能,就向语言添加该功能并不是一个好主意。只有真正需要时才能添加该功能。C#设计团队已经在数组协变 上犯过这个错误。