这个问题来自编译器实现的角度。
我想知道C#中的静态变量,我找到了解释它们未实现的原因(这里:http://blogs.msdn.com/b/csharpfaq/archive/2004/05/11/why-doesn-t-c-support-static-method-variables.aspx)。
引用“通过拥有类级静态可以获得几乎相同的效果” - 这让我很好奇,有什么区别?假设C#将具有静态变量语法 - 实现可能是“将其静态推送为静态字段并保留条件初始化(如果需要)”。完成。
我唯一能看到的是给定初始化时值类型的问题。还有什么适合“差不多”吗?
我重新解释了这个问题 - 如何使用仅现有功能在C#编译器中实现静态变量(因此静态变量必须在当前状态条件下内部生成)。
答案 0 :(得分:3)
实际上很容易检查编译器在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
{
// Code size 73 (0x49)
.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
} // end .try
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
} // end handler
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
} // end of method Class1::M3
看起来更复杂。第二个静态字段,看起来像一个关键部分(虽然我找不到有关_Init_thread_*
方法的任何信息)。
它看起来不再那么微不足道了。性能也受到影响。恕我直言,不在C#中实现静态变量是一个很好的决定。
总结一下,
为了支持静态变量,C#编译器必须:
它似乎并不多,但如果你结合这样的几个特征,复杂性会呈指数级增长。
你得到的唯一回报是一个简单的,编译器提供的,线程安全的初始化。
仅将语言功能添加到语言中并不是一个好主意,因为其他语言支持它。在真正需要时添加功能。 C#设计团队已经使用array covariance
犯了这个错误答案 1 :(得分:1)
我的想法是你需要开始在初始化器上加上“隐形”锁。
考虑两个线程同时类Foo.UseStatic;
的情况class Foo
{
static int counter = 0;
void UsesStatic()
{
static int bar = (counter++) + (counter++);
}
}
基于bar
的{{1}}初始化可能是一个线程噩梦。 (请查看interlocked类。)
如果十个并发线程调用此代码,则bar可能会以任何旧代码结束。一把锁可以稳定东西但是你已经插入了这个大的钝性能障碍而没有用户的说法。
编辑:新方案已添加。
@greenoldman的评论表明可以处理这个简单的例子。但是C#充满了语法糖,它被转化为不同的“基本”结构。例如,闭包被转换为带有字段的类,counter++
语句变为try / finally块,等待的调用变为传递回调,迭代器方法变为状态机。
当发生静态变量初始化时,编译器必须处理任何特殊情况吗?我们有信心这会起作用吗?
using
我的猜测是,C#团队对此进行了研究,并认为“这是一个纯粹的错误来源”,并且完全没了。