一.const与readonly的争议
你一定写过const,也一定用过readonly,但说起两者的区别,并说出何时用const,何时用readonly,你是否能清晰有条理地说出个一二三?
const与readonly之所以有如此争议,是因为彼此都存在"不可改变"这一特性,对于二者而言,我们需要关心的是,什么时候开始不可变?什么是不可改变的?这就引出了我们下面要讨论的话题.
二.什么时候开始不可变?
我们先抛出结论.
const在程序运行的任何时候都是不可变的,无论什么时候开始,什么时候结束,它的值是固化在代码中的,我们称之为编译期常量;
readonly在某个具体实例第一次初始时指定它的值(出了构造函数后,对于这个实例而言,它就不能改变)或者是作为静态成员在运行时加载它的值,我们称之为运行时常量.
我们先谈const:
1.const由于其值从不变化,我们称之为常量,常量总是静态的,因此const是天然static的,我们不能再用static修饰const.如下图所示:
正确的定义应该是const float PI=3.14159F;
2.const既然是静态的,因此它属于整个类,而不属于某个实例,我们可以直接通过类名来调用,如下所示:
3.由于常量的值是直接嵌入代码的,因此在运行时不需要为常量分配任何内存,也不能获取常量的地址,也不能以传引用的方式传递常量.
什么叫直接嵌入代码?即:在编译的过程中,编译器首先将常量值保存到程序集元数据中,在引用常量的地方,编译器将提取这个常量值并嵌入生成的IL代码中,这也就是为什么常量不需要分配任何内存的原因.
我们来验证一下上面的结论,首先我们定义一个常量:
1 public class MathHelper 2 { 3 public const float PI= 3.14159F; 4 }
调用:
1 static void Main(string[] args) 2 { 3 float pi= MathHelper.PI; 4 }
我们查看生成的IL代码,如下:
标红的那一行,即是将PI的值直接嵌入代码之中.理解这一点不难,但是这种写法会带来潜在的问题:const不能支持很好支持程序集的跨版本.为了说明这个问题,我们需要对我们的代码进行如下的改造:
第一步:我们将MathHelper单独放到一个项目中,并生成一个单独的程序集(程序集版本:1.0).
第二步:我们编译应用程序为exe文件,采用上面的方法来查看IL代码,我们看到const的值仍然嵌入了代码之中.
第三步:我们修改PI的值为3.14,重新编译MathHelper,生成一个单独的程序集(程序集版本:2.0).
第四步:因为我们只是重新编译了MathHelper所在的程序集,没有重新编译exe文件,我们查看exe的IL代码,发现嵌入代码的值仍为3.14159.
也就是在跨程序集的引用中,当改变了常量时,除非重新编译所有引用了常量的程序集,否则改变不能体现在引用当中.
虽然有了这样的bug隐患,也不是说const就一无是处,由于const在程序中不占用内存,所以它的速度非常之快,于是我们在设计程序时,如果一个值从不变化,我们可以将其定义常量来寻求速度上的效率上的提升.比如我们程序需要国际化的时候,简体中文的编码为2052,美国英语的编码为1033,我们可以将它们定义为常量.
另外,我们说过常量是没有地址的,因而不能以传引用的方式传递常量,即下面的写法是错误的:
说完const,我们来说readonly
1.readonly是实例的,因此通过类名是不可直接访问readonly变量的
定义: