zoukankan      html  css  js  c++  java
  • const与readonly

          尽管你写了很多年的C#的代码,但是可能当别人问到你const与readonly的区别时候,还是会小小的愣一会吧~

          笔者也是在看欧立奇版的《.Net 程序员面试宝典》的时候,才发现自己长久以来竟然在弄不清出两者的情况下,混用了这么长的时间。的确,const与readonly 很像,都是将变量声明为只读,且在变量初始化后就不可改写。那么,const与readonly 这两个修饰符到底区别在什么地方呢?其实,这个牵扯出C#语言中两种不同的常量类型:静态常量(compile-time constants)和动态常量(runtime constants)。这两者具有不同的特性,错误的使用不仅会损失效率,而且还会造成错误。

          首先先解释下什么是静态常量以及什么是动态常量。静态常量是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值。而动态常量的值则是在运行的那一刻才获得的,编译器编译期间将其标示为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化。

          当你大致了解上面的两个概念的时候,那么就可以来说明const与readonly了。const修饰的常量是上述中的第一种,即静态常量;而readonly则是第二种,即动态常量。那么区别可以通过静态常量与动态常量的特性来说明:

          1)const修饰的常量在声明的时候必须初始化;readonly修饰的常量则可以延迟到构造函数初始化

          2)const修饰的常量在编译期间就被解析,即常量值被替换成初始化的值;readonly修饰的常量则延迟到运行的时候

          此外const常量既可以声明在类中也可以在函数体内,但是static readonly常量只能声明在类中。

         

          可能通过上述纯概念性的讲解,对有些初学者有些晕乎。下面就一些例子来说明下:     

    using System;
    class P
    {
       
    static readonly int A=B*10;
       
    static readonly int B=10;  
        
    public static void Main(string[] args)
        {
            Console.WriteLine(
    "A is {0},B is {1} ",A,B);
        }
    }

          对于上述代码,输出结果是多少?很多人会认为是A is 100,B is 10吧!其实,正确的输出结果是A is 0,B is 10。好吧,如果改成下面的话:

    <!--

    Code highlighting produced by Actipro CodeHighlighter (freeware)
    http://www.CodeHighlighter.com/

    -->using System;
    class P
    {
       
    const int A=B*10;
       
    const int B=10;  
       
    public static void Main(string[] args)
        {
            Console.WriteLine(
    "A is {0},B is {1} ",A,B);
        }
    }

           对于上述代码,输出结果又是多少呢?难道是A is 0,B is 10?其实又错了,这次正确的输出结果是A is 100,B is 10。

           那么为什么是这样的呢?其实在上面说了,const是静态常量,所以在编译的时候就将A与B的值确定下来了(即B变量时10,而A=B*10=10*10=100),那么Main函数中的输出当然是A is 100,B is 10啦。而static readonly则是动态常量,变量的值在编译期间不予以解析,所以开始都是默认值,像A与B都是int类型,故都是0。而在程序执行到A=B*10;所以A=0*10=0,程序接着执行到B=10这句时候,才会真正的B的初值10赋给B。如果,你还是不大清楚的话,我们可以借助于微软提供的ILDASM工具,只需在Vs 2008 Command下输入ILDASM就可以打开,如下所示:

           

           

            分别打开上述两个代码编译后产生的可执行文件,如下图所示:

                          

                       static readonly可执行程序的结构                                                                const可执行程序的结构

            在上述两张图中都可以看到A与B常量,分别双击节点可以看出其中的差异:

                 

                       static readonly修饰的常量A                                                                      const修饰的常量A

       

              

                      static readonly修饰的常量B                                                                       const修饰的常量B

             从上图中可以看出,const修饰的常量在编译期间便已将A,B的字面值算出来了,而static readonly修饰的常量则未解析,所以在Main函数中有以下的区别:

                       

                                  static readonly程序的Main函数                                                            const程序的Main函数

          从Main函数中我们可以看出,const的那个程序的输出直接是100与10,而readonly在输出的时候确实P::A与P::B,即将A与B常量的值延迟到运行的时候才去确定,故输出是0与10。

          那么对于静态常量以及动态常量还有什么特性呢?其实,静态常量只能被声明为简单的数据类型(int以及浮点型)、枚举、布尔或者字符串型,而动态常量则除了这些类型,还可以修饰一些对象类型。如DateTime类型,如下:

          //错误

          const DateTime time=new DateTime();

          //正确

          static readonly DateTime time=new DateTime();

         上述错误在于不能使用new关键字初始化一个静态常量,即便是一个值类型,因为new将会导致到运行时才能确定值,与静态变量编译时就确定字面值有悖。    

          欧书上最后给出了对静态常量与动态常量之间的比较,如下表所示:     

         

    一、常数

      常数是一个表示恒定不变的值的符号,定义一个常数符号时,我们必须能够在编译时确定它的值。通过编译后,编译器将常数的值保存在其所定义模块的元数据内。这意味着常数的类型只能是那些编译器认为的基元类型。(注:因为只有基元类型的数据才能利用文本常数,在编译时直接进行初始化。而非基元类型的数据成员只能在运行时调用构造器来完成初始化。)另一个需要注意的是常数总被认为是类型(而非实例)的一部分,这在常数值恒定不变的含义下很容易理解。

         在C#中,下面的类型被称为基元类型,可以被用来定义常数:bool,char,byte,sbyte,decimal,int16,int32,int64,uint64,single,double,以及string。(注,枚举类型由于本身以基元类型新式存储,故也可以被用来定义常数,虽然它不是基元类型)。

      当使用常数符号时,编译器首先从定义常数的模块的元数据中查找出该符号,直接取出常数的值,然后将之嵌入到编译后的IL代码中。因为常数的值是直接嵌入到代码中的,所以常数的值是直接嵌入到代码中的,所以常数在运行时不再分配任何的内存分配。另外,我们也不能获取常数的地址,或者以引用的方式来传递一个常数。这些约束还意味着常数没有一个良好的跨模块版本特性。也就是说只有当确信一个符号的值永远不会改变时,才应该用常数来定义。

    首先将下面的代码编译成DLL程序集:

    <!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> namespace Com { public class Component { //注意:C#不允许为常数制定static关键字 //因为常数隐含是static public const int MaxEntriesInList = 600; } }

    引用上面得到的程序集,将下面的代码编译成一个应用程序:

    <!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->namespace TestCom { class Program { static void Main(string[] args) { Console.WriteLine(Com.Component.MaxEntriesInList); } } }

    上面的代码清晰的展示了常数所隐含的版本问题。如果我们把前面DLL程序集中MaxEntriesInList常数值修改为500后并重新编译该DLL程序集,后面的应用程序代码将不会受到任何影响。要获取新的常数值,我们必须重新编译应用程序(调试时按Ctrl+F5和直接按F5就可以看到区别,Ctrl+F5编译加运行,F5,不重新编译,直接运行),如果要求一个模块中的数值能够在运行时(而不是编译时)被另一个模块获取,那么就不应该使用常数。相反,我们应该使用只读字段(关于这个问题还可以查与effective C#关于const和readonly的区别那一章,个人感觉讲的特别的详细)。

    二、字段

      字段又称数据成员,它保存着一个值类型的实例、或者一个指向引用类型的引用。CLR支持类型(静态)和实例(非静态)两种字段。对于类型字段,系统在该类型被加载进入一个应用程序域时为其分配动态内存,这通常发生在引用该类型的方法第一次被JIT编译时。对于实例字段,系统在该类型的实例被构建时为其分配动态内存。

      因为字段是以动态内存的形式存储的,因此只能在运行时刻获取它们的值。字段也没有常数的版本问题,另外字段可以是任何类型,没有像常数那么的限制。

      CLR支持只读和读写两种字段。大多数字段都是读写字段,这意味着代码在执行过程中字段可以被多次赋值。但是只读字段只能在构造器中被赋值(构造器在对象初次创建时被执行,且只执行一次,值得注意的是构造器内部只读字段可以被多次赋值。这里所指的是实例只读字段和实例构造器,对于静态只读字段,则只能在静态构造器中赋值,静态构造器在该类型初次被引用时执行)。

      下面是用静态只读字段来解决”常数“的版本问题。新版DLL程序集如下:

    <!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->namespace Com { public class Component { //类型字段需要static关键字 public static readonly int MaxEntriesInList = 60000; } }

    这是唯一需要修改的地方,Program累无需改变。

      当将MaxEntriesInList值修改为20000时,并重新编译该DLL程序集。当再次执行Program时,输出将变为20000,值得指出的时,Program程序代码并没有被重新编译,仅仅是再次运行了一遍

    查看effective C#关于readonly和const总结:

    • readonly为运行时常量,const为编译时常量(上面代码已经证明);
    • 编译时常量被运行时常量快,性能好,但是缺乏灵活性(上面已经证明,编译时常量需要重新编译应用程序);
    • 编译时常量(const)仅限于数值和字符串(基元类型),C#不允许使用new来初始化一个编译时常量;
    • const修饰的常量默认是静态的(类型);
    • readonly修饰的字段可以在构造函数中被修改;
    • 使用const较之使用readonly的唯一好处就是性能
  • 相关阅读:
    Oracle SQL语句大全—查看表空间
    Class to disable copy and assign constructor
    在moss上自己总结了点小经验。。高手可以飘过 转贴
    在MOSS中直接嵌入ASP.NET Page zt
    Project Web Access 2007自定义FORM验证登录实现 zt
    SharePoint Portal Server 2003 中的单一登录 zt
    vs2008 开发 MOSS 顺序工作流
    VS2008开发MOSS工作流几个需要注意的地方
    向MOSS页面中添加服务器端代码的另外一种方式 zt
    状态机工作流的 SpecialPermissions
  • 原文地址:https://www.cnblogs.com/leestar54/p/3013226.html
Copyright © 2011-2022 走看看