首页 未命名正文

linux编程_C#基础知识之const与readonly

云返利网 未命名 2020-05-26 09:07:15 9 0

一.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变量的 界说:
1  public class MathHelper
2 {
3       public readonly float PI;
4 }
接见:   2.readonly出了组织函数,对于这个实例而言就不能改变,因此下面的写法也是错误的      既然,我们强调"出了组织函数",那是不是意味着,我们在构建函数内部,可以一次或多次改变它的值?为了验证我们的料想,我们对MathHelper革新如下:
1 public class MathHelper
2 {
3       public MathHelper()
4       {
5             this.PI = 3.15F;
6             this.PI = 3.14F;
7        }
8       public readonly float PI;
9 }
挪用代码:
1 static void Main(string[] args)
2 {
3       MathHelper m = new MathHelper();
4        Console.WriteLine(m.PI);
5 }
输出效果: 从以上的效果,我们可以看出,在组织函数中可以对readonly变量多次赋值,但一旦出了构建函数则是只读的.   3.有了第2点的支持,下面我们可以验证readonly是实例的(不能变的第一种情形)这一结论,我们现在来验证这个结论.    我们革新MathHelper如下:    
1 public class MathHelper
2 {
3    public MathHelper(float pi)
4    {
5       this.PI = pi;
6    }
7    public readonly float PI;
8 }
挪用如下:
 1 static void Main(string[] args)
 2 {
 3      MathHelper m1 = new MathHelper(3.14F);
 4      Console.WriteLine(m1.PI);
 5 
 6      MathHelper m2 = new MathHelper(3.15F);
 7      Console.WriteLine(m2.PI);
 8 
 9      Console.Read();
10 }
输出效果: 我们实例化了两个差别的MathHelper,给PI赋予了差别的值,PI的值属于差别的实例,这也就验证了我们的结论.   4.readonly的内联写法 那有的童鞋说了,我还用过这样的写法,这说明了readonly可以在构建方式外赋值.如下所示:
1 public class MathHelper
2 {
3     public readonly float PI=3.15F;
4 }
     实在,这是一种内联写法,是C#的一种语法糖,只是一种语法上的简化,现实它们也是在组织方式中举行初始化的.C#允许使用这种简化的内联初始化语法来初始化类的常量、read/write字段和readonly字段。   5.readonly赋值的第二种情形:若是我用static修饰readonly会发生什么?      前面讲const时,我们说过const是静态的,这种静态不能以显式指定,因此在const前加static会导致编译器编译失败.那我们把static修饰readonly会发生什么样的效果?       首先,我们确定,静态的是属于类的,此时的readonly我们不能通过组织函数来指定.
1 public class MathHelper
2 {
3     public static readonly float PI=3.14F;
4 }
挪用:
1 static void Main(string[] args)
2 {
3      Console.WriteLine(MathHelper.PI);
4      Console.Read();
5 }
效果与我们预期的一致:   但我们的疑问不会就此打住:既然static readonly也是属于类的,而且它的值也不能通过组织函数来赋值,那么编译器会像const一样把它的值写入IL代码中么?我们反编译其IL代码如下:         可以看到,这里并没有将值嵌入到代码当中.       因此,我们可以勇敢地展望, 这种写法不会造成支持程序集的跨版本问题.这里就不写验证的历程,留给列位读者同伙自行探索.       既然没有嵌入代码中,那么在程序运行的时刻,它的值是在什么时刻分配内存的呢?       我们引用《CLR via C#(第4版)》中的一句话来说明这个问题: 对于静态字段,其动态内存是在类中分配的,而类是在类型加载到AppDomain时建立的,那么,什么时刻将类型加载到AppDomain中呢?谜底是:通常是在引用了该类型的任何方式首次举行JIT编译的时刻.而对于前面第3点中的实例字段来说,其动态内存是在组织类型的实例时分配的.        

三.什么是不能变的?

      前面我们花了大量的篇幅说明const与readonly的变量什么时刻才最先不能变,有的从一最先就不能变,有的是第一次加载的时刻不能变,有的是出了组织函数后不能变,然则我们有一个十分要害的问题没有弄清楚:什么东西不能变?也许童鞋们很疑惑,值不能变呗!这话不完全对.       要想明了这个问题,我们需要明了const与readonly修饰的工具,也就是我们稳定的内容.       const可以修饰基元类型:Boolean、Char、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Single、Double、Decimal和String。也可以修改类class,但要把值设置为null。不能以修饰struct,由于struct是值类型,不能以为null.       对于基元类型来说,值是存储在栈上的,因此我们可以以为稳定的是值自己,这里string是一个特殊的引用类型,这里它也存在值类型的特征,因此也可以以为它稳定的是值自己.       对于readonly而言,readonly可以修饰任何类型.对于基元类型而言,我们可以以为它与const无异,然则对于引用类型,我们需要郑重看待,不能想当然,下面我们通过实验来得出结论:
1 public class Alphabet
2 {
3         public static readonly Char[] Letters = new Char[] {'A','B','C','D','E','F' };
4 }
挪用:
 1 static void Main(string[] args)
 2 {
 3      Alphabet.Letters[0] = 'a';
 4      Alphabet.Letters[0] = 'b';
 5      Alphabet.Letters[0] = 'c';
 6      Alphabet.Letters[0] = 'd';
 7      Alphabet.Letters[0] = 'e';
 8      Alphabet.Letters[0] = 'f';
 9      Console.WriteLine(Alphabet.Letters.Length);
10      Console.Read();
11  }
可赋值!!! 输出效果如下:   现在,我们给它赋予一个新的工具: 不能赋值!!! 看到这里你是不是心里有谜底了?   结论:对于引用类型而言,我们可以赋值,而不能以赋予一个新的工具,由于这里稳定的是引用,而不是引用的工具.         到此,我们的const与readonly的庖丁解牛式的剖析也就告一段落了,说了这么多,我们实在也就是想说明以下2点:     1.const任何时刻都稳定,比readonly快,但不能解决跨版本程序集问题,readonly静态时在第一次JIT编译后稳定,实例时在出了实例的组织函数后不能变.     2.const修饰基元类型,稳定的是值;readonly修饰值类型时,其值稳定,修改引用类型时,其引用稳定.     以上.

【关于云返利网】

云返利网是阿里云、腾讯云、华为云产品推广返利平台,在各个品牌云产品官网优惠活动之外,云返利网还提供返利。您可以无门槛获得阿里云、华为云、腾讯云所有产品返利,在官网下单后就可以领取,无论是自己用、公司用还是帮客户采购,您个人都可以获得返利。云返利网的目标是让返利更多、更快、更简单!详情咨询13121395187(微信同号)