Effective C# 原则2:为你的常量选择readonly而不是const(译)

转载 2007年09月26日 17:22:00
对于常量,C#里有两个不同的版本:运行时常量和编译时常量。
因为他们有不同的表现行为,所以当你使用不当时,将会损伤程序性能或者出现错误。
两害相权取其轻,当我们不得不选择一个的时候,我们宁可选择一个运行慢一点但正确的那一个,而不是运行快一点但有错误的那个。基于这个理由,你应该选择运行时常量而不是编译时常量(译注:这里隐藏的说明了编译时常量效率更高,但可能会有错误)。
编译时常量更快更直接,但在可维护性上远不及运行时常量。保留编译时常量是为了满足那些对性能要求克刻,且随着程序运行时间的过去,其值永远不发生改变的常量使用的(译注:这说明编译时常量是可以不被C#采用的,但考虑到性能问题,还是做了保留)。
你可以用关键字readonly来声明(declare)一个运行时常量,编译时常量是用关键字const声明的。
//Compile time constant:
public cocnst int _Millennium = 2000;
//Runtime constant:
public static readonly int _ThisYear = 2007;//(译注:原文为2004)
编译时常量与运行时常量不同之处表现在如何对他们的访问上。
一个编译时常量会被目标代码中的值直接取代。下面的代码:
if(myDateTime.Year == _Millennium)
会与下面写的代码编译成完全相同的IL代码:
if(myDateTime.Year == 2000)

运行时常量的值是在运行时确定的。当你引用一个只读常量时(read-only)IL会为你引用一个运行时常量的变量,而不是直接使用该值。
当你任意的使用其中一个常量时,这些区别就在一些限制上表现出来。编译时常量只能是基本类型(primitive types)(built-in integral and floating-poing types),枚举或者是字符串。这些就是你只能给运行时常量在初始化时赋值的类型。这些基本类就是可以被编译器在编译IL代码时直接用真实的值所取代的数据类型。下面的代码块(construct)不能通过编译。你不能用new运算符初始化一个编译时常量,即使这个数据类型是值类型。

//Does not complie, use readonly instead:
private const DateTime _classCreation = new DateTime(2000,1,1,0,0,0);

(译注:DateTime是一个值类型数据,但上面的代码因为用了new运算符,编译器无法在编译确定具体的对象应该用什么样的实际值来取代,所以无法通过编译。)

编译时常量仅限于数字和字符串。只读变量,也就是运行时常量,在构造函数(constructor)执行完成后它们是不以能被修改的。但只读变量是所有不同的,因为他们是在运行时才赋值的。当你使用运行时常量时,你有更大的可伸缩性。有一点要注意的是,运行时常量可以是任何类型的数据。而且你必须在构造函数里对他们初始化,或者你可以用任何一个初始化函数来完成。你可以添加一个DateTime结构的只读变量(--运行时常量),但你不能添加一个DateTime结构的(编译时)常量。

你可以把每一个实例(的常量)指定为只读的,从而为每一个类的实例存放不同的值。与编译时常量不同的是,它只能是静态的。
(译注:简单的讲,运行时常量可以是一个类的实例成员,也可以是一个类型的静态成员,而编译时常量只能是静态成员,因此类似:static const string m_name;的代码是不能通过编译的。)

只读数据最重要的区别是他们在运行时才确定值。当你使用只读变量 时,IL会为你产生一个对只读变量引用,而不是直接产生数值。随着时间的推移,这个区别在(系统)维护上有深远的潜在影响。
编译时常量生成的IL代码就跟直接使用数值时生成的IL是一样的,即使是在跨程序集时:一个程序集里的编译时常量在另一个程序集会保留着同样的值(译注:这里说的不是很清楚,看后面的这个例子可能会更清楚一些)。
编译时常量和运行时常量的赋值方法对运行时的兼容性有所影响。
假设你已经在程序集Infrastructure中同时定义了一个const和一个readonly变量:

public class UserfulValues...{
 
public static readonly int StartValue = 5;
 
public const int EndValue = 10;
}

同时,在另一个程序集(译注:这个程序集认为是我们做测试的应用程序的程序集,下面所说的应用程序的程序集都是指的这个程序集)中,你引用了这些值:

for(int i=UserfulValues.StartValue;i<UserfulValues.EndValue;i++)...{
 Console.WriteLine(
"value is {0}",i);
}

如果你运行这个简单测试程序,你可以看到下面明显的结果:
value is 5
value is 6
...
value is 9
过后,你又为程序集Infrastructure发布了个新的版本,并做了如下的修改:

public class UserfulValues...{
 
public static readonly int StartValue = 105;
 
public const int EndValue = 120;
}

你单独的发布了程序集Infrastructure而没有全部编译你的程序,你希望得到下面的:
value is 105
value is 106
...
value is 119
事实上,你什么也得不到。上面的循环已经是用105开始而用10来结束。C#编译器(在编译时)把常量用10来代替应用程序的程序集中的使用,而不是用常量EndValue所存储的值。而常量StartValue的值,它是被申明为只读的,它可以在运行时重新读取该常量的值。因此,应用程序的程序集可以在不用重新编译的情况下使用新的数据,简单的编译一下Infrastructure程序集,然后重新布署安装一下,就足够让你的客户可能使用这些新的数据了。更新的编译时常量应该看成是接口的变化。你必须重新编译所有引用到编译时常量的代码。更新的运行时常量则可以当成是实现的改变,这于在客户端已经存在的二进制代码是兼容的。用MSIL解释一下前面的那个循环里发生了什么:
IL_0000: ldsfld int32 Chapter1.UserfulValues::StartValue
IL_0005: stloc.0
IL_0006: br.s IL_001c
IL_0008: ldstr "value is {0}"
IL_000d: ldloc.0
IL_000e: box [mscrolib]System.Int32
IL_0013: call void [mscrolib]System.Console::WriteLine(string,object)
IL_0018: ldloc.0
IL_0019: ldc.i4.1
IL_001a: add
IL_001b: stloc.0
IL_001c: ldloc.0
IL_001d: ldc.i4.s 10
IL_001f: blt.s IL_0008
从MSIL命令清单的最上面一行你可以看到,StartValue(的值)是动态载入的。
但是,在MSIL命令的最后,结束条件是把值10当成硬代码(hard-coded)使用的。

另一方面,有些时候你也须要为某些值使用编译时常量。例如:考虑一个须要识别不同版本的续列化情形。用来标识一个特殊版本号的常量应该是一个编译时常量,它们决不会发生改变。而当前版本号则应该是一个运行时常量,在不同的版本发布后会有所改变。

private const int VERSION_1_0 = 0x0100;
private const int VERSION_1_1 = 0x0101;
private const int VERSION_1_2 = 0x0102;
//major release;
private const int VERSION_2_0 = 0x0200;
//Chech for the current version:
private static readonly int CURRENT_VERSION = VERSION_2_0;

在每次存盘时,你用运行常量来保存当前版本号。

//Read fom persistent storage, check stored version against complie-time constant:
protected MyType(SerializationInfo info, StreamingContext cntxt)...{
 
int storedVersion = info.GetInt32("VERSION");
 
switch(storedVersion)...{
 
case VERSION_2_0:
  readVersion2(info,cntxt);
  
break;
 
case VERSION_1_1:
  readVersion1(info,cntxt);
  
break;
 
//etc.  
 }

}


//Write the current version:
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo inf,StreamingContext cxt)...{
 
//use runtime constant for currnet version
 inf.AddValue("VERSION",CURRENT_VERSION);
 
//
 
//write remaining delements...
}


最后一个有利的原因而使我们要使用编译时常量,就是它的性能。比起运行时常量,已知的编译时常量可以更直接有效的被访问。然而,性能上的功效是甚微的,并且应该与可伸缩性的降低进行一个权衡。Be sure to profile performace differences before giveing up the flexibility.

const的值必须在编译时被确定,(它们可以是):属性参数,枚举定义,以及一小部份你认为应该定义一个值且该值不能在不同的版本发布时发生改变的常量。
无论如何,宁愿选择伸缩性更强的运行时常量。 

Effective C# 原则2:为你的常量选择readonly而不是const

原则2:为你的常量选择readonly而不是const对于常量,C#里有两个不同的版本:运行时常量和编译时常量。因为他们有不同的表现行为,所以当你使用不当时,将会损伤程序性能或者出现错误。两害相权取其...
  • jimung
  • jimung
  • 2008年04月12日 13:08
  • 220

为你的常量选择readonly而不是const

      对于常量,C#里有两个不同的版本:运行时常量和编译时常量。      因为他们有不同的表现行为,所以你使用不当时,将会损伤程序性能或者出现错误。      当我们不得不选择一个的时候,我们...
  • heling0223
  • heling0223
  • 2010年07月07日 14:12
  • 311

C#: const与readonly的用法和区别解析

总结一下const和readonly:const和readonly的值一旦初始化则都不再可以改写; const只能在声明时初始化;readonly既可以在声明时初始化也可以在构造器中初始化; cons...
  • xiaofengsheng
  • xiaofengsheng
  • 2009年10月12日 19:51
  • 4551

原则2:为你的常量选择readonly而不是const

原则2:为你的常量选择readonly而不是const对于常量,C#里有两个不同的版本:运行时常量和编译时常量。因为他们有不同的表现行为,所以当你使用不当时,将会损伤程序性能或者出现错误。两害相权取其...
  • wenzhoufeng
  • wenzhoufeng
  • 2007年10月23日 10:01
  • 448

C#中const与readonly的区别之浅见

之前在写C#代码时一直没有认真思考过const与readonly的区别,都是用的比较随意,只知道说这两个关键字一个是不变常量并且无法修改,一个是只读不能进行写操作。然后看过一些文章之后觉得之前理解的太...
  • andyqingliu
  • andyqingliu
  • 2017年01月11日 17:32
  • 3610

#define 和 const 定义常量比较。

在C++中可以使用#define或者const来定义常量,但是使用const相比起#define有更多的优点。——–以下部分内容选自 const 常量有数据类型,而宏常量是没有数据类型。编译器可以对前...
  • y1196645376
  • y1196645376
  • 2017年03月02日 20:06
  • 643

C#中的readonly跟const用法小结

总结一下常量和只读字段的区别: 一.值的区别: 常量(const):是已知的,不能修改的值。const都是静态的,不能使用static修饰。 只读字段(readonly):不能修改的,只读字段的...
  • u013930409
  • u013930409
  • 2015年05月24日 17:31
  • 2977

C#——const与readonly的区别

文章1: 我们都知道,const和static readonly的确很像:通过类名而不是对象名进行访问,在程序中只读等等。在多数情况下可以混用。二者本质的区别在于,const的值是在编译期间确定的,因...
  • shineHoo
  • shineHoo
  • 2010年01月26日 09:52
  • 6973

C#中的static、readonly与const的比较

[csharp] view plaincopy  C#中有两种常量类型,分别为readonly(运行时常量)与const(编译时常量),本文将就这两种类型的不同特性进行比较并说明各自的适...
  • zhangsu_
  • zhangsu_
  • 2014年03月16日 15:23
  • 3706

C#:readonly和const用法小结

我们都知道,使用const关键字可以将字段或局部变量声明为常量,其不能被修改。 readonly关键字被用来声明只读字段,主要用于字段。 两者有一定的相似性,但是也有明显的差异性,所以本文将两者结合在...
  • tiana0
  • tiana0
  • 2013年09月08日 18:15
  • 2734
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Effective C# 原则2:为你的常量选择readonly而不是const(译)
举报原因:
原因补充:

(最多只允许输入30个字)