发现一段很诡异的C#代码,见识了静态构造函数这种奇怪的东西:
先补习下吧:
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的值……啊,要疯掉了……应该是多少呢?不敢确定了吧,交给编译器来运行看看吧~~
结果如下:
修改下代码,看看内部到底怎么运行的:
执行结果如下:
看到这个结果,对C#更加迷惑了。类A中静态构造函数调用得到的B.Y,居然是0。这是什么道理?难道这个时候B还没出生,但是B.Y已经出生了?不管了,先接受了吧,以后玩C#还是小心点了。哪位神仙指点指点迷津啊?
- 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#还是小心点了。哪位神仙指点指点迷津啊?
- 01:42
- 浏览 (1115)
- 论坛浏览 (8789)
- 评论 (59)
- 分类: c#
- 相关推荐
评论
59 楼 mooniscrazy 2009-01-25 引用
自己好好跟踪一下代码的执行过程就行了。恕我直言,lz恐怕根本就没有多少编程经验。这 段代码并不复杂,稍有经验的程序员(不管是java还是c#)都应该可以正确的预测代码的执行结果。静态构造函数,就是该类静态成员第一次被访问的时候执 行,类的所有内存,初始值都是0。理解了这点,就可以正确的预测结果了。
不是老讲这个诡异那个恶心之类的废话。起码,先把东西搞懂了再发表评论不迟。
lz之所以惊诧,就是因为不理解静态构造函数的原理,所以大惊小怪,以为发现了什么了不得的bug。其实,任何带静态构造函数的编程语言都是这样设计的。
不是老讲这个诡异那个恶心之类的废话。起码,先把东西搞懂了再发表评论不迟。
lz之所以惊诧,就是因为不理解静态构造函数的原理,所以大惊小怪,以为发现了什么了不得的bug。其实,任何带静态构造函数的编程语言都是这样设计的。
与语言无关。一切运行在冯诺依曼结构的计算机上的程序,数据(变量)总得存在一个地方, 如内存、寄存器等,那么,从物理存储方式来说,一个字节,我们只能确定他的每一个bit是0还是1,并不存在一个bit不是0也不是1——一个字节的所有 bit都定了,这个字节的值也就定了。也就是说,你定义了一个变量,没办法,不能只有地址没有值。
我们实际上定义不了一个没有值的变量。高级语言也避免不了这个问题,不过增加了一些限制或默认措施来解决这个问题:原生类型的默认值,高级类型的初始化……
我们实际上定义不了一个没有值的变量。高级语言也避免不了这个问题,不过增加了一些限制或默认措施来解决这个问题:原生类型的默认值,高级类型的初始化……
mooniscrazy 写道
无语,这种基础问题也上升到怀疑anders水准的高度。
貌 似anders不是此问题的始作俑者吧,怀疑不到他老人家的头上。另外我下面的回复,已经说明我自己最初的态度跟帖子本身有差别了,但是我绝不会把帖子给 改了,所以请讨论问题本身。没有问题就请路过吧。所谓好读书不求甚解,静态这种东西,现在也没有心思去琢磨。本意是抛砖引玉,自己抛了次砖,所以也不怕别 人向我抛几次砖。
另外我觉得“递归到内存溢出或者死锁”,也许真的合理一点,可以防止程序员这么交叉使用,用某种规定来避免,似乎太技巧化了。
56 楼 mooniscrazy 2009-01-16 引用
无语,这种基础问题也上升到怀疑anders水准的高度。
55 楼 zyongsheng83 2009-01-16 引用
或许有人觉得递归到内存溢出或者死锁更合理
treenode 写道
你的问题就是不理解
实际上编译器载入一个类的时候分两个步骤,1.将类加载到内存堆的某个位置,这个时候所有静态成员的值都是0或者null;2.如果该类定义了静 态构造方法,则执行该方法。当你调用A的静态方法时,实际上编译器已经完成了第一步的动作,而第二步还没有完成,所以成员的值仍然是0。很正常的现象,有 什么好诡异的?
你说C++不是,但不知道你说的是哪个C++编译器,我刚才用TC++试了试(现在手上只有这个),结果一样是1,2。
引用
难道这个时候B还没出生,但是B.Y已经出生了
实际上编译器载入一个类的时候分两个步骤,1.将类加载到内存堆的某个位置,这个时候所有静态成员的值都是0或者null;2.如果该类定义了静 态构造方法,则执行该方法。当你调用A的静态方法时,实际上编译器已经完成了第一步的动作,而第二步还没有完成,所以成员的值仍然是0。很正常的现象,有 什么好诡异的?
你说C++不是,但不知道你说的是哪个C++编译器,我刚才用TC++试了试(现在手上只有这个),结果一样是1,2。
结合以上所说的,改成以下的代码就一目了然了:
using System;
namespace StaticTest
{
class A
{
public static int X;
static A()
{
Console.WriteLine("calling A");
Console.WriteLine("B.Y是:{0}",B.Y);
X = B.Y + 1;
}
static void Main()
{
Console.WriteLine("X={0}, Y={1}", A.X, B.Y);
}
}
class B
{
public static int Y = A.X + 1;
static B()
{
Console.WriteLine("calling B");
Console.WriteLine("Y等于:{0}",Y);
}
}
}
- using System;
- class A
- {
- public static int X = B.Y + 1;
- }
- class B
- {
- public static int Y = A.X + 2;
- }
- class MainClass
- {
- public static void Main(string[] args)
- {
- Console.WriteLine("A.X={1},B.Y={0}", B.Y, A.X);
- Console.Read();
- }
- }
把输出顺序改了一下,输出的是1,3。
- using System;
- class A
- {
- public static int X = B.Y + 1;
- }
- class B
- {
- public static int Y = C.Z + 2;
- }
- class C
- {
- public static int Z = A.X + 3;
- }
- class MainClass
- {
- public static void Main(string[] args)
- {
- Console.WriteLine("A.X={0},B.Y={1},C.Z={2}", A.X, B.Y,C.Z);
- Console.Read();
- }
- }
尝试变为3各类输出,6,5,3.
- using System;
- class A
- {
- public static int X = B.Y + 1;
- }
- class B
- {
- public static int Y = C.Z + A.X;
- }
- class C
- {
- public static int Z = A.X + 3;
- }
- class MainClass
- {
- public static void Main(string[] args)
- {
- Console.WriteLine("A.X={0},B.Y={1},C.Z={2}", A.X, B.Y,C.Z);
- Console.Read();
- }
- }
输出4,3,3.
- using System;
- class A
- {
- public static int X = B.Y + 1;
- }
- class B
- {
- public static int Y = A.X+C.Z ;
- }
- class C
- {
- public static int Z = A.X + B.Y;
- }
- class MainClass
- {
- public static void Main(string[] args)
- {
- Console.WriteLine("A.X={0},B.Y={1},C.Z={2}", A.X, B.Y,C.Z);
- Console.Read();
- }
- }
输出1,0,0.
改为string类型
- using System;
- class A
- {
- public static string X = B.Y + "A";
- }
- class B
- {
- public static string Y = A.X+"B" ;
- }
- class C
- {
- public static string Z = A.X + B.Y;
- }
- class MainClass
- {
- public static void Main(string[] args)
- {
- Console.WriteLine("A.X={0},B.Y={1},C.Z={2}", A.X, B.Y,C.Z);
- Console.Read();
- }
- }
可见编译器在检测到某个静态变量的自我引用时使用默认值不再向下进行运算,至于元素在内存里的操作流程得请会使sos得高人出来说一下,没想到楼主偾世了一下引来这么多愤世...
楼主的思维陷入了泥潭中。帮楼主清理一下。
在不考虑“人”的因素的情况下
1、语言规范指定的有问题么?如果有问题,问题在哪里?如果没问题,进入下一步。
2、C#的对规范的实现有问题么?同上
3、给出的例子,在C#的语法上有问题么?
经过以上清理,我得出的结论是:明知恶心的代码,却偏偏要用它的人有问题。
而在实际开发中,倒是有可能出现类似的情况,这也是楼主担忧的问题。
但活人不能被尿憋死,只要你知道问题在哪里,就总会有办法解决。
在不考虑“人”的因素的情况下
1、语言规范指定的有问题么?如果有问题,问题在哪里?如果没问题,进入下一步。
2、C#的对规范的实现有问题么?同上
3、给出的例子,在C#的语法上有问题么?
经过以上清理,我得出的结论是:明知恶心的代码,却偏偏要用它的人有问题。
而在实际开发中,倒是有可能出现类似的情况,这也是楼主担忧的问题。
但活人不能被尿憋死,只要你知道问题在哪里,就总会有办法解决。
ulpyuoo 给的地址里面有句::
这说明B.Y已经初始化为0。这时候不要去考虑Y = A.X + 1,这时A类可能还没构造捏, 所以Y=0就对了。给Y赋值0后,才去执行A类的静态构造,接着是B的静态构造。接着是main执行。
于是出现X=1,Y=2。
假设我们把main函数移到A类里,则结果是:X=2,Y=1。
引用
如果类中包含用来开始执行的 Main 方法,则该类的静态构造函数将在调用 Main 方法之前执行。如果类包含任何带有初始值设定项的静态字段,则在执行该类的静态构造函数时,先要按照文本顺序执行那些初始值设定项。
这说明B.Y已经初始化为0。这时候不要去考虑Y = A.X + 1,这时A类可能还没构造捏, 所以Y=0就对了。给Y赋值0后,才去执行A类的静态构造,接着是B的静态构造。接着是main执行。
于是出现X=1,Y=2。
假设我们把main函数移到A类里,则结果是:X=2,Y=1。
47 楼 hurricane1026 2009-01-09 引用
我从听说static constructor到用它也就是20分钟。这个帖子真长。
46 楼 terrysunhh 2009-01-06 引用
这不是bug,但是这种交互使用静态变量在实际项目开发中不可取,具体没什么难度,两个互相引用的静态变量只能调用对方一次,否则就是死环循,MS没这么傻
mooniscrazy 写道
刚刚学编程,就把自己当大师了。知道anders是谁吗?
不就是开发delphi那个吗,borland传奇之类的,好像我接触最早的开发工具就是borland c++ (5.0?忘了 2000年的时候了), 那公司差不多已经倒闭了,拜托把这里帖子都看完再回复
44 楼 mooniscrazy 2009-01-05 引用
刚刚学编程,就把自己当大师了。知道anders是谁吗?
C#的静态构造跟Java的static代码块是一回事。这是非常重要的OO特性,本身没什么好恶心的。
关于静态变量的初始化顺序问题,也不要觉得C#恶心,其实真正恶心人的是C++的全局变量【静态变量跟全局变量一回事,不过全局变量更恶心人,更 容易冲突,更没有访问保护】初始化顺序。C#明确规定了初始化顺序,而C++的规定是:无规定,所以,你完全没有可以依赖的东西,导致你【迫使你】把全局 变量都转换为函数,而函数返回一个静态变量,更恶心人的地方在于这个函数要实现为inline函数【Cpper一般都要这么干,否则对不起自己的帽子】的 话,你知道你的编译器怎么对付返回静态变量的内联函数的吗?虽然标准规定了必须返回那个独一份的东西,可是就有编译器对于inline函数,各返回各的 copy。
而初始化顺序问题一定要有一个规定,C#不过是一个规定而已,如果你觉得这个顺序不好,你给出一个更好的顺序,反正像C++这种鸵鸟式的方式绝对不是办法。
关于静态变量的初始化顺序问题,也不要觉得C#恶心,其实真正恶心人的是C++的全局变量【静态变量跟全局变量一回事,不过全局变量更恶心人,更 容易冲突,更没有访问保护】初始化顺序。C#明确规定了初始化顺序,而C++的规定是:无规定,所以,你完全没有可以依赖的东西,导致你【迫使你】把全局 变量都转换为函数,而函数返回一个静态变量,更恶心人的地方在于这个函数要实现为inline函数【Cpper一般都要这么干,否则对不起自己的帽子】的 话,你知道你的编译器怎么对付返回静态变量的内联函数的吗?虽然标准规定了必须返回那个独一份的东西,可是就有编译器对于inline函数,各返回各的 copy。
而初始化顺序问题一定要有一个规定,C#不过是一个规定而已,如果你觉得这个顺序不好,你给出一个更好的顺序,反正像C++这种鸵鸟式的方式绝对不是办法。
42 楼 yyliuliang 2008-12-31 引用
这帖子居然还两票良好贴 。。。。
装个vs 文件夹里就附带有c# language specification 花上半个小时就能得到解答的疑惑 居然也能开个帖子讨论这么半天
装个vs 文件夹里就附带有c# language specification 花上半个小时就能得到解答的疑惑 居然也能开个帖子讨论这么半天
40 楼 subwayline13 2008-12-31 引用
嗯,楼上的,还真是你说的啊,受教了。不过我还真没跨程序集调过const,而且const的值基本上都没变过。