`
willzh
  • 浏览: 300899 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

C#静态变量的诡异与恶心

    博客分类:
  • c#
阅读更多
发现一段很诡异的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#还是小心点了。哪位神仙指点指点迷津啊?



分享到:
评论
55 楼 mooniscrazy 2009-01-25  
自己好好跟踪一下代码的执行过程就行了。恕我直言,lz恐怕根本就没有多少编程经验。这段代码并不复杂,稍有经验的程序员(不管是java还是c#)都应该可以正确的预测代码的执行结果。静态构造函数,就是该类静态成员第一次被访问的时候执行,类的所有内存,初始值都是0。理解了这点,就可以正确的预测结果了。
不是老讲这个诡异那个恶心之类的废话。起码,先把东西搞懂了再发表评论不迟。
lz之所以惊诧,就是因为不理解静态构造函数的原理,所以大惊小怪,以为发现了什么了不得的bug。其实,任何带静态构造函数的编程语言都是这样设计的。
54 楼 launchin 2009-01-22  
与语言无关。一切运行在冯诺依曼结构的计算机上的程序,数据(变量)总得存在一个地方,如内存、寄存器等,那么,从物理存储方式来说,一个字节,我们只能确定他的每一个bit是0还是1,并不存在一个bit不是0也不是1——一个字节的所有bit都定了,这个字节的值也就定了。也就是说,你定义了一个变量,没办法,不能只有地址没有值。
我们实际上定义不了一个没有值的变量。高级语言也避免不了这个问题,不过增加了一些限制或默认措施来解决这个问题:原生类型的默认值,高级类型的初始化……
53 楼 willzh 2009-01-18  
mooniscrazy 写道

无语,这种基础问题也上升到怀疑anders水准的高度。


貌似anders不是此问题的始作俑者吧,怀疑不到他老人家的头上。另外我下面的回复,已经说明我自己最初的态度跟帖子本身有差别了,但是我绝不会把帖子给改了,所以请讨论问题本身。没有问题就请路过吧。所谓好读书不求甚解,静态这种东西,现在也没有心思去琢磨。本意是抛砖引玉,自己抛了次砖,所以也不怕别人向我抛几次砖。

另外我觉得“递归到内存溢出或者死锁”,也许真的合理一点,可以防止程序员这么交叉使用,用某种规定来避免,似乎太技巧化了。
52 楼 mooniscrazy 2009-01-16  
无语,这种基础问题也上升到怀疑anders水准的高度。
51 楼 zyongsheng83 2009-01-16  
或许有人觉得递归到内存溢出或者死锁更合理
50 楼 qhfrose 2009-01-15  
试了下,Java也是一样的结果。
49 楼 asd300 2009-01-15  
treenode 写道
你的问题就是不理解

引用
难道这个时候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);

        }  
       
    }  
}  
48 楼 careprad 2009-01-15  
你对c#有偏见,c#比java仅从设计上讲要好很多,你为什么对java不恶心?
47 楼 sdhjl2000 2009-01-14  
 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得高人出来说一下,没想到楼主偾世了一下引来这么多愤世...
46 楼 lazy 2009-01-13  
楼主的思维陷入了泥潭中。帮楼主清理一下。
在不考虑“人”的因素的情况下
1、语言规范指定的有问题么?如果有问题,问题在哪里?如果没问题,进入下一步。
2、C#的对规范的实现有问题么?同上
3、给出的例子,在C#的语法上有问题么?
经过以上清理,我得出的结论是:明知恶心的代码,却偏偏要用它的人有问题。
而在实际开发中,倒是有可能出现类似的情况,这也是楼主担忧的问题。
但活人不能被尿憋死,只要你知道问题在哪里,就总会有办法解决。
45 楼 木哥哥 2009-01-13  
ulpyuoo 给的地址里面有句::

引用
如果类中包含用来开始执行的 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。

44 楼 kxscr 2009-01-12  
毫无意义的争论

43 楼 hurricane1026 2009-01-09  
我从听说static constructor到用它也就是20分钟。这个帖子真长。
42 楼 terrysunhh 2009-01-06  
这不是bug,但是这种交互使用静态变量在实际项目开发中不可取,具体没什么难度,两个互相引用的静态变量只能调用对方一次,否则就是死环循,MS没这么傻
41 楼 willzh 2009-01-05  
mooniscrazy 写道

刚刚学编程,就把自己当大师了。知道anders是谁吗?


不就是开发delphi那个吗,borland传奇之类的,好像我接触最早的开发工具就是borland c++ (5.0?忘了 2000年的时候了), 那公司差不多已经倒闭了,拜托把这里帖子都看完再回复
40 楼 mooniscrazy 2009-01-05  
刚刚学编程,就把自己当大师了。知道anders是谁吗?
39 楼 fixopen 2008-12-31  
C#的静态构造跟Java的static代码块是一回事。这是非常重要的OO特性,本身没什么好恶心的。

关于静态变量的初始化顺序问题,也不要觉得C#恶心,其实真正恶心人的是C++的全局变量【静态变量跟全局变量一回事,不过全局变量更恶心人,更容易冲突,更没有访问保护】初始化顺序。C#明确规定了初始化顺序,而C++的规定是:无规定,所以,你完全没有可以依赖的东西,导致你【迫使你】把全局变量都转换为函数,而函数返回一个静态变量,更恶心人的地方在于这个函数要实现为inline函数【Cpper一般都要这么干,否则对不起自己的帽子】的话,你知道你的编译器怎么对付返回静态变量的内联函数的吗?虽然标准规定了必须返回那个独一份的东西,可是就有编译器对于inline函数,各返回各的copy。

而初始化顺序问题一定要有一个规定,C#不过是一个规定而已,如果你觉得这个顺序不好,你给出一个更好的顺序,反正像C++这种鸵鸟式的方式绝对不是办法。
38 楼 yyliuliang 2008-12-31  
这帖子居然还两票良好贴  。。。。
装个vs 文件夹里就附带有c# language specification  花上半个小时就能得到解答的疑惑  居然也能开个帖子讨论这么半天
37 楼 xsc963 2008-12-31  
语言不是一个完美的东西,请慎用!
交叉引用是一种很不好的编程习惯!而且现实在完全可以避免,所以这个问题不存在!世界任何东西都不是完美的!
36 楼 幸存者 2008-12-30  
subwayline13 写道

不要用JAVA的思路带到C#里面来,一般C#里是这样滴
    using System;

    class A
    {
        public const int X = 1 + B.Y;
    }
    class B
    {
        public const int Y = 2 + C.Z + A.X;
    }
    class C
    {
        public const int Z = A.X + 4;
    }
    class D
    {
        public const int W = C.Z + B.Y + A.X;
    }


如果你写成这样,C#编译器就给打住了,别写这么诡异的代码,呵呵。

const还是慎用,尤其是跨程序集调用的时候。

相关推荐

    C# 公有变量 私有变量 静态变量

    本文将深入探讨C#中的公有变量(public)、私有变量(private)和静态变量(static),并结合20171028的C#编程实践进行说明。 **公有变量(public)** 公有变量是可以被程序任何部分访问的成员,无论是在同一类中...

    C#中静态变量的使用

    C#中静态变量的使用 C# 中静态变量的使用是指在 C# 语言中使用静态变量的方法和技术。本文将详细介绍静态变量与非静态变量的区别、静态变量的使用方式、静态构造函数的使用等方面的知识点。 一、静态变量与非静态...

    c#静态变量和实例变量

    有关静态变量和实例变量的资料,有详细的说明,大家可以看一下。我这里 有大量的关于c#的资料 要的话可以私信我

    C#静态变量与实例变量实例分析

    本文实例讲述了C#静态变量与实例变量的具体用法,分享给大家供大家参考。具体分析如下: 1)在语法定义上的区别: 类变量也叫静态变量,静态变量前要加static关键字,而实例变量前则不加; 实例变量也叫对象变量,...

    WPF绑定静态变量的示例代码(二)加上IValueConverter

    标题中的“WPF绑定静态变量的示例代码(二)加上IValueConverter”指出了一个具体的编程任务:在WPF应用中,我们需要将静态变量与UI元素绑定,并且利用IValueConverter来处理数据转换,以便于控件能够正确地反映出...

    WPF绑定静态变量的示例代码

    在C#中,静态变量属于类,而不是类的实例。这意味着,无论创建了多少对象,静态变量只有一个副本,所有对象共享这个副本。在WPF中,我们可能会遇到需要将控件与这样的全局状态关联的情况,这时绑定到静态变量就变得...

    C#静态调用webservice

    本篇文章将深入探讨如何在C#中静态调用Web Service,以便通过Web Service来访问数据库。Web Service是一种基于互联网的软件服务,允许不同系统间的应用程序进行交互。在描述的场景中,我们构建了一个IIS(Internet ...

    浅析C#静态类,静态构造函数,静态变量

    C#中的静态类、静态构造函数和静态变量是编程中重要的概念,它们在程序设计中起着关键的作用。 首先,让我们来理解静态类。在C#中,静态类是一种特殊的类,它不能被实例化,只能通过类名直接调用其静态成员。静态类...

    C#静态方法与非静态方法的比较

    ### C#静态方法与非静态方法的比较 在C#编程语言中,静态方法与非静态(实例)方法是两种非常重要的方法定义方式。这两种方法各有特点,在不同的场景下使用可以带来不同的效果。 #### 一、概念理解 **1. 静态方法...

    C#中static静态变量的用法实例

    本文实例讲述了C#中static静态变量的用法。分享给大家供大家参考。具体如下: 使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员static修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,...

    C# 静态变量与静态方法实例研究

    在C#编程中,静态变量和静态方法是两种重要的特性,它们在程序设计中有着特定的应用场景和作用。本文将通过实例分析这两种概念,并探讨如何优化程序性能。 首先,我们来看一下静态变量。静态变量是属于类级别的,而...

    C#通过变量名与倍福plc相连

    C#通过变量名与倍福PLC相连,是一种常见的实现设备控制和数据交换的方法。 首先,我们需要了解C#如何进行串行或网络通信。这通常涉及到使用System.Net命名空间中的Socket类或System.IO.Ports命名空间中的SerialPort...

    VB.NET局部静态变量介绍

    VB.NET能够实现很多C#不能做到的功能,如When语句、Optional参数、局部Static变量、对象实例访问静态方法、Handles绑定事件、On Error处理异常、Object直接后期绑定等等。VB和C#同属.NET的语言,编译出来的是同样的...

    C#事件监视变量变化

    C#事件监视变量变化,事件推送机制,用于变量绑定场景,后台出发前台更新操作

    c#变量的作用和使用方法

    在C#编程语言中,变量是...综上所述,C#中的变量是存储数据的基础,理解其声明、类型、作用域、生命周期以及与其他语言元素的交互对于编程至关重要。通过阅读"第四章成员变量和常量.doc",你应该能获得更深入的理解。

    C#基础知识 静态

    在C#编程语言中,"静态"(Static)是一个非常重要的关键字,它有着广泛的用途,涉及到类、方法、变量等多个方面。本篇文章将深入探讨C#中的静态概念及其应用。 首先,静态类(Static Class)是不能被实例化的,它们...

    c#编程设置环境变量

    `Environment`命名空间是.NET框架提供的一部分,它包含了与当前执行环境相关的静态类和方法。这些方法包括获取、设置和检查环境变量。下面,我们将深入探讨如何使用`Environment`类来操作环境变量。 1. **获取环境...

    C#基础变量与数据类型的文档

    本文档聚焦于C#的基础变量与数据类型,这是学习任何编程语言的第一步,也是构建强大程序的基石。 1. 变量:在C#中,变量可以被视为存储数据的容器。每当你需要在程序中存储一个值时,就需要声明一个变量。声明变量...

Global site tag (gtag.js) - Google Analytics