锁定老帖子 主题:C#静态变量的诡异与恶心
精华帖 (0) :: 良好帖 (2) :: 新手帖 (2) :: 隐藏帖 (2)
|
|
---|---|
作者 | 正文 |
发表时间:2008-12-27
最后修改:2008-12-28
using System; namespace StaticTest { class A { public static int X; static A() { X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() { } static void Main() { Console.WriteLine("X={0}, Y={1}", A.X, B.Y); } } } 先补习下吧: 1、静态构造函数既没有访问修饰符,也没有参数。因为是.NET调用的,所以像public和private等修饰符就没有意义了。 2、是在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类,也就是说我们无法直接调用静态构造函数,也就无法控制什么时候执行静态构造函数了。 3、一个类只能有一个静态构造函数。 4、无参数的构造函数可以与静态构造函数共存。尽管参数列表相同,但一个属于类,一个属于实例,所以不会冲突。 5、最多只运行一次。 6、静态构造函数不可以被继承。 7、如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。 好,复习完毕,哪位同学回答下上面程序的输出结果是多少? 还是搞不明白吧:) 唔,你明白了?他没明白,我也没明白…… class A静态构造函数中的B.Y好像很奇怪,貌似要确定A.X的值,得先确定B.Y的值,而B.Y的值在B中却是由A.X来确定的,那A.X的值……啊,要疯掉了……应该是多少呢?不敢确定了吧,交给编译器来运行看看吧~~ 结果如下: X=1, Y=2 修改下代码,看看内部到底怎么运行的: using System; namespace StaticTest { class A { public static int X; static A() { Console.WriteLine("calling A"); Console.WriteLine(B.Y); X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() { Console.WriteLine("calling B"); Console.WriteLine(Y); } static void Main() { Console.WriteLine("X={0}, Y={1}", A.X, B.Y); } } } 执行结果如下: calling A 0 calling B 2 X=1, Y=2 看到这个结果,对C#更加迷惑了。类A中静态构造函数调用得到的B.Y,居然是0。这是什么道理?难道这个时候B还没出生,但是B.Y已经出生了?不管了,先接受了吧,以后玩C#还是小心点了。哪位神仙指点指点迷津啊? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-12-27
这样看来,貌似是顺序执行了。呵呵。
|
|
返回顶楼 | |
发表时间:2008-12-27
抛开语言不说,这样的交叉引用本身就是一个设计的bug、
为什么这样子,估计得看看IL或是C#规范 |
|
返回顶楼 | |
发表时间:2008-12-27
查看与此联系人的全部对话记录
Iris 说: Kimm King(秦风意动) 说: 48$01$ Iris 说: 下上午好 Kimm King(秦风意动) 说: http://www.iteye.com/topic/305583 Kimm King(秦风意动) 说: 看看这个 Kimm King(秦风意动) 说: 静态构造函数 Kimm King(秦风意动) 说: 很诡异 Iris 说: 怎么了嘛 Iris 说: 这个很奇怪吗? Kimm King(秦风意动) 说: 挺奇怪的 我找个reflector看看IL Kimm King(秦风意动) 说: 不能理解 Iris 说: 静态构造函数的运行顺序IL里面看不出来的 Iris 说: 哥哥死心吧~~ Iris 说: 不过是一个 beforefieldsinit 元数据标志而已 Kimm King(秦风意动) 说: B.Y为什么是0呢? Kimm King(秦风意动) 说: 在A里调用时, Iris 说: 等下哦,樱桃把语言规范中的一些资料给哥哥 Kimm King(秦风意动) 说: ok~ Iris 说: 仅仅从代码本身来说 Iris 说: 哥哥能解释这个现象木 Iris 说: 么 Iris 说: 若要初始化新的封闭式类类型,需要先为该特定的封闭类型创建一组新的静态字段(第 10.5.1 节)。将其中的每个静态字段初始化为默认值(第 5.2 节)。下一步,为这些静态字段执行静态字段初始值设定项(第 10.4.5.1 节)。最后,执行静态构造函数。 Iris 说: 明白了吧 Iris 说: IL在构造类的时候,字段都是会自动设置为默认值的 Iris 说: 呃……应该是CLR构造类的时候 Iris 说: 参看语言规范484页 Iris 说: 例子和给的代码一模一样 Iris 说: 有一段详细的解释 Kimm King(秦风意动) 说: 如果说 类被加载时 x和y都是0. Iris 说: 要执行 Main 方法,系统在运行类 B 的静态构造函数之前首先要运行 B.Y 的初始值设定项。因为引用了 A.X 的值,所以 Y 的初始值设定项导致运行 A 的静态构造函数。这样,A 的静态构造函数将继续计算 X 的值,从而获取 Y 的默认值 0,而 A.X 被初始化为 1。这样就完成了运行 A 的静态字段初始值设定项和静态构造函数的进程,控制返回到 Y 的初始值的计算,计算结果变为 2。 Kimm King(秦风意动) 说: 发给我一份 规范 Iris 说: VS安装目录由 Iris 说: VS/VC#/Specifications/2052/ Kimm King(秦风意动) 说: thanks Iris 说: 要看过语言规范才能算深入理解^^ Kimm King(秦风意动) 说: 明白了,我也正奇怪,main在B中有没有影响 Iris 说: 是这样的 |
|
返回顶楼 | |
发表时间:2008-12-27
如果类型存在静态的交叉引用
那么交叉链的单向终点 所有前向的被交叉对象 都为以默认值处理 |
|
返回顶楼 | |
发表时间:2008-12-27
这种静态构造函数的设计,太霸道了,霸道得不可理喻。就跟kimmking所说,跟bug无异。我估计引入静态构造函数,是因为在实现解释器的时候发现了一个设计缺陷,但又很难避免,便强制规定在循环调用的第二次获取静态变量的默认值,以防止陷入死循环一样的状态。而,语言本身对静态成员的描述是,在每个类的静态变量成员调用之前,静态构造函数已经执行过一次,且最多执行一次。貌似就算C#语法上隐藏静态构造函数,实际上内部还是有这么一个东西存在,蠢蠢欲动以初始化静态构造。
对C#有点失望,我只是说C#,并非指.net或者mono,难怪微软要指定F#为新的一等公民…… |
|
返回顶楼 | |
发表时间:2008-12-27
莫名其妙。好吧我跟你讲,你的代码在Java里面也是输出同样的结果,于是你就继续恶心吧。
|
|
返回顶楼 | |
发表时间:2008-12-27
treenode 写道 莫名其妙。好吧我跟你讲,你的代码在Java里面也是输出同样的结果,于是你就继续恶心吧。 写了段Java的代码: class StaticTest { public static void main(String[] args){ System.out.println(Integer.toString(A.X) +" "+Integer.toString(B.Y)); } } class A { static int X = B.Y + 1; } class B { static int Y = A.X + 1; } treenode你说的一点没错,既然你不觉得恶心,那你知道为什么吗?或者内部的机理 |
|
返回顶楼 | |
发表时间:2008-12-27
应该不是由于Java也有类似的“缺陷”,就觉得习以为常了吧?我倒觉得如果C++也这样,那就更奇怪了……阿弥陀佛,幸好不是
|
|
返回顶楼 | |
发表时间:2008-12-27
你的问题就是不理解
引用 难道这个时候B还没出生,但是B.Y已经出生了 实际上编译器载入一个类的时候分两个步骤,1.将类加载到内存堆的某个位置,这个时候所有静态成员的值都是0或者null;2.如果该类定义了静态构造方法,则执行该方法。当你调用A的静态方法时,实际上编译器已经完成了第一步的动作,而第二步还没有完成,所以成员的值仍然是0。很正常的现象,有什么好诡异的? 你说C++不是,但不知道你说的是哪个C++编译器,我刚才用TC++试了试(现在手上只有这个),结果一样是1,2。 |
|
返回顶楼 | |