首页 > 代码库 > const和readonly差别

const和readonly差别

我们都知道,const和static readonly的确非常像:通过类名而不是对象名进行訪问,在程序中仅仅读等等。在多数情况下能够混用。二者本质的差别在于,const的值是在编译期间确定的,因此仅仅能在声明时通过常量表达式指定其值。而static readonly是在执行时计算出其值的,所以还能够通过静态构造函数来赋值。明确了这个本质差别,我们就不难看出以下的语句中static readonly和const是否能互换了:

1. static readonly MyClass myins = new MyClass();
2. static readonly MyClass myins = null;
3. static readonly B = 10;   static readonly A = B * 20;
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
5. void SomeFunction()
   {
      const int a = 10;
      ...
   }
6.private static string astr="abcd";
  private const string str = astr+"efg";
1:不能够换成const。new操作符是须要运行构造函数的,所以无法在编译期间确定
2:能够换成const。我们也看到,Reference类型的常量(除了String)仅仅能是Null。
3:能够换成const。我们能够在编译期间非常明白的说,A等于200。
4:不能够换成const。道理和1是一样的,尽管看起来1,2,3的数组的确就是一个常量。
5:不能够换成readonly,readonly仅仅能用来修饰类的field,不能修饰局部变量,也不能修饰property等其它类成员。

6.错误:假设在astr前加上const或者const改为readonly就可以;

总结:1.const、readonly和static readonly定义的常量,指定初始值后(包含在构造函数内指定的初始值) 将不可更改,可读不可写;
        2.const定义时必须指定初始值,而readonly定义时能够不进行初始化(MS建议在定义时初始值),同一时候也能够在构造函数内指定初始值,

          并以构造函数内指定的值为准;

        3.const和static readonly定义的常量是静态的,仅仅能由类直接訪问;而readonly定义的常量是非静态 的,仅仅能由实例对象訪问;  
        4.static readonly常量,假设在构造函数内指定初始值,则必须是静态无參构造函数;
        5.const是编译时常量,readonly是执行时常量;cosnt较高效,readonly较灵活。在应用上以static readonly取代const,以平衡const在灵活性上的不足,
           同一时候克服编译器优化cosnt性能,所带来的程序集引用不一致问题;  

 

 

文章2:

 

readonly和const比較

前天犯了个低级错误,是关于readonly的,总结了一下:   
C#的readonlykeyword仅仅能在字段上面使用
public readonly TcpClient client;
不能在类,方法,属性上面使用readonly!!
顺便看了一下readonly和const的差别:

  • readonlyconst都是用来标识常量的。
  • const可用于修饰class的field或者一个局部变量(local variable);而readonly只用于修饰class的field。
  • const常量的值必然在编译时就已明白而且恒定的;而readonly常量却有一点不同,那就是其值能够在执行时编译,当然,它也必须遵守作为常量的约束,那就是值必须恒定不变。
  • const常量必须在声明的同一时候对其进行赋值,而且确保该值在编译时可确定并恒定;而readonly常量则能够依据情况选择在声明的同一时候对其赋予一个编译时确定并恒定的值,或者将其值的初始化工作交给实例构造函数(instant constructor)完毕。如:public readonly string m_Now = DateTime.Now.ToString();,m_Now会随着执行时实际情况变化而变化。
  • const常量属于类级别(class level)而不是实例对象级别(instant object level),而且它不能跟static结合一起使用,该常量的值将由整个类的全部实例对象共同分享(具体论述參见后面的Remark区域)。
  • readonly常量既能够是类级别也能够是实例对象级别的,这取决于它的声明以及初始化工作怎么实施。readonly能够与static结合使用,用于指定该常量属于类级别,而且把初始化工作交由静态构造函数(static constructor)完毕(有关怎样把readonly常量声明为类级别或实例对象级别的论述清參见后面的Remark区域)
  • 能被const修饰声明为常量的类型必须是下面的基元类型(primitive type):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, float, bool, decimal, string
  • object, 数组(Array)和结构(struct)不能被声明为const常量。
  • 普通情况下,引用类型是不能被声明为const常量的,只是有一个例外:string。该引用类型const常量的值能够有两种情况,stringnull。事实上,string尽管是引用类型,可是.NET却对它特别处理,这样的处理叫做字符串恒定性(immutable),使得string的值具有仅仅读特性。有关字符串恒定性的内容,能够參考《Microsoft .NET框架程序设计(修订版)》

 

Examples:

using System;

public class Order
{
    
public Order()
    
{
        Guid guid = Guid.NewGuid();
        ID = guid.ToString("D");
    }


    
// 对于每一份订单,其订单序号都是实时确定的常量。
    public readonly string ID;

    
public override string ToString()
    
{
        
return "Order ID: " + ID;
    }

}

Explaintion:

  • 假设结合数据库使用,ID field通常都会都会与某个表的主健(primary key)关联起来,如Orders表的OrderID。
  • 数据库的主健通常採用下面三种方式:
    • 自己主动递增值。你能够通过把DataColumn.AutoIncrement设定为true值来激活自己主动递增特性。
    • 唯一名称。这个是使用自定义的算法来生成一个唯一序列号。
    • GUID(全局唯一标识符)。你能够通过System.Guid结构来生成GUID,如上例。

using System;

class
 Customer
{
    
public Customer(string name, int
 kind)
    
{
        m_Name 
=
 name;
        m_Kind 
=
 kind;
    }


    
public const int NORMAL = 0;
    
public const int VIP = 1
;
    
public const int SUPER_VIP = 2
;

    
private string
 m_Name;
    
public string
 Name
    
{
        
get return m_Name; }

    }


    
private readonly int m_Kind;
    
public
 int Kind
    
{
        
get return m_Kind; }

    }


    
public override string ToString()
    
{
        
if(m_Kind ==
 SUPER_VIP)
            
return "Name: " + m_Name + "[SuperVip]"
;
        
else if(m_Kind ==
 VIP)
            
return "Name: " + m_Name + "[Vip]"
;
        
else

            
return "Name: " + m_Name + "[Normal]";
    }

}

 


Remarks:

  • 普通情况下,假设你须要声明的常量是普遍公认的并作为单个使用,比如圆周率,黄金切割比例等。你能够考虑使用const常量,如:public const double PI = 3.1415926;。假设你须要声明常量,只是这个常量会随着实际的执行情况而决定,那么,readonly常量将会是一个不错的选择,比如上面第一个样例的订单号Order.ID。
  • 另外,假设要表示对象内部的默认值的话,而这类值一般是常量性质的,那么也能够考虑const。很多其它时候我们对源码进行重构时(使用Replace Magic Number with Symbolic Constant),要去除魔数(Magic Number)的影响都会借助于const的这样的特性。
  • 对于readonlyconst所修饰的变量到底是属于类级别的还是实例对象级别的问题,我们先看看例如以下代码:

Using directives

namespace ConstantLab
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            Constant c 
= new Constant(3);
            Console.WriteLine(
"ConstInt = " + Constant.ConstInt.ToString());
            Console.WriteLine(
"ReadonlyInt = " + c.ReadonlyInt.ToString());
            Console.WriteLine(
"InstantReadonlyInt = " + c.InstantReadonlyInt.ToString());
            Console.WriteLine(
"StaticReadonlyInt = " + Constant.StaticReadonlyInt.ToString());

            Console.WriteLine(
"Press any key to continue");
            Console.ReadLine();
        }

    }


    
class Constant
    
{
        
public Constant(int instantReadonlyInt)
        
{
            InstantReadonlyInt 
= instantReadonlyInt;
        }


        
public const int ConstInt = 0;

        
public readonly int ReadonlyInt = 1;

        
public readonly int InstantReadonlyInt;

        
public static readonly int StaticReadonlyInt = 4;
    }

}

 

  • 使用Visual C#在Main()里面使用IntelliSence插入Constant的相关field的时候,发现ReadonlyInt和InstantReadonlyInt须要指定Constant的实例对象;而ConstInt和StaticReadonlyInt却要指定Constant class(參见上面代码)。可见,用const或者static readonly修饰的常量是属于类级别的;而readonly修饰的,不管是直接通过赋值来初始化或者在实例构造函数里初始化,都属于实例对象级别。
  • 普通情况下,假设你须要表达一组相关的编译时确定常量,你能够考虑使用枚举类型(enum),而不是把多个const常量直接嵌入到class中作为field,只是这两种方式没有绝对的孰优孰劣之分。

 

using System;

enum
 CustomerKind
{
    SuperVip,
    Vip,
    Normal
}


class Customer
{
    
public Customer(string
 name, CustomerKind kind)
    
{
        m_Name 
=
 name;
        m_Kind 
=
 kind;
    }


    
private string m_Name;
    
public string
 Name
    
{
        
get return m_Name; }

    }


    
private CustomerKind m_Kind;
    
public
 CustomerKind Kind
    
{
        
get return m_Kind; }

    }


    
public override string ToString()
    
{
        
return "Name: " + m_Name + "[" + m_Kind.ToString() + "]"
;
    }

}

 

  • 然而,当这样的结合使用枚举和条件推断的代码阻碍了你进行更灵活的扩展,并有可能导致日后的维护成本添加,你能够代之以多态,使用Replace Conditional with Polymorphism来对代码进行重构。(有关多态的具体介绍,请參见《今天你多态了吗?》一文。)

 

Comments:

  • readonly field准确来说应该翻译成为“仅仅读域”,这里是为了统一翻译用语才将它和const两者所修饰的量都说成“常量”,希望没有引起误会。

===============================================================================

 有时你不想在在程序执行时改变域,如执行时程序依靠的数据文件,一个math类的pi值,或者不论什么在程序执行时你不想改变的值。为了处理这种情况,c#中定义了两个近似的、相关的成员类型:contants和read-only域。  
   
  Constants域  
   
  从字面能够看出,常数(用keywordconst表示)在应用程序执行期间保持不变。当定义某个事物为const时记住两个规则:第一,定义成常数的成员的值是在编译时设置的——或者是由编程者指定,或者是由编译器设置缺省值;第二,一个常数成员的值必须被写为一个字面常数。  
  为了定义一个常数域,在要定义的成员前使用一个constkeyword,例如以下所看到的:  
   
  using   System;  
   
  class   MagicNumbers  
  {  
  public   const   double   pi   =   3.1415;  
  public   const   int   answerToAllLifesQuestions   =   42;  
  }  
   
  class   ConstApp  
  {  
  public   static   void   Main()  
  {  
  Console.WriteLine("pi   =   {0},   everything   else   =   {1}",  
  MagicNumbers.pi,   MagicNumbers.answerToAllLifesQuestions);  
  }  
  }  
   
  请注意这个代码的一个关键点——不必在client实例化MagicNumbers类,由于const成员缺省是静态的。为了更清楚的展示它,我们列出这两个域在MSIL中生成时的情况:  
   
  answerToAllLifesQuestions   :   public   static   literal   int32   =   int32(0x0000002A)  
  pi   :   public   static   literal   float64   =   float64(3.1415000000000002)  
   
  Read-only   域  
   
  使用const域是很实用的,由于他清楚的表明了编程者的意图。然而,这仅仅能用于在编译时已经知道值的情况下。所以,当一个域仅仅有在执行时才干知道值并且一旦初始化以后值便不能改变的情况下,编程者该怎么办呢?C#语言的设计者用Read-only域的办法攻克了这个问题(在其它语言中一般不会处理)。  
  当你用readonlykeyword定义了一个域时,你仅仅能在一个地方设置这个域的值——构造函数。以后,域的值便不能被自身类或者使用这个类的客户改变。让我们看一个图形应用程序记录屏幕分辨率的样例。你不能用const处理这个问题,由于应用程序直到执行时才干确定终端用户的屏幕分辨率,所以你必须使用这种代码:  
   
  using   System;  
   
  class   GraphicsPackage  
  {  
  public   readonly   int   ScreenWidth;  
  public   readonly   int   ScreenHeight;  
   
  public   GraphicsPackage()  
  {  
  this.ScreenWidth   =   1024;  
  this.ScreenHeight   =   768;  
  }  
  }  
   
  class   ReadOnlyApp  
  {  
  public   static   void   Main()  
  {  
  GraphicsPackage   graphics   =   new   GraphicsPackage();  
  Console.WriteLine("Width   =   {0},   Height   =   {1}",  
  graphics.ScreenWidth,    
  graphics.ScreenHeight);  
  }  
  }  
   
  猛一看,这个代码正是我们须要的。然而,另一点小问题:我们定义的readonly域是一个实例域,这意味着是用户在使用域之前必须先实例化类。这可能也不是什么问题,甚至这正是你想做的——在实例化类的时候正能够初始化readonly域的值。但假设你想要一个被定义成静态的、能在执行时被初始化的常数时该怎么办呢?这时,我们能够定义一个带static和readonly修饰符的域。然后,创建一个构造函数的特殊类型——static   constructor。静态函数能够用来初始化static域、readonly域或其它的域。如今我们改变先前的样例,给屏幕分辨率域加上static和readonly,并且添加一个静态构造函数。  
   
  using   System;  
   
  class   GraphicsPackage  
  {  
  public   static   readonly   int   ScreenWidth;  
  public   static   readonly   int   ScreenHeight;  
   
  static   GraphicsPackage()  
  {  
  //   Code   would   be   here   to    
  //   calculate   resolution.  
  ScreenWidth   =   1024;  
  ScreenHeight   =   768;  
  }  
  }  
   
  class   ReadOnlyApp  
  {  
  public   static   void   Main()  
  {  
  Console.WriteLine("Width   =   {0},   Height   =   {1}",    
  GraphicsPackage.ScreenWidth,    
  GraphicsPackage.ScreenHeight);  
  }  
  }