Effective C# 2:Prefer readonly to const

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 2 Prefer readonly to const

优先于const使用readonly

C# has two different versions of constants: compile-time constants and runtime constants. They have very different behaviors, and using the wrong one will cost you performance or correctness. Neither problem is a good one to have, but if you must pick one, a slower, correct program is better than a faster, broken program. For that reason, you should prefer runtime constants over compile-time constants. Compile-time constants are slightly faster, but far less flexible, than runtime constants. Reserve the compile-time constants for when performance is critical and the value of the constant will never change over time.

C#有两种不同版本的常量:编译时常量和运行时常量。它们具有非常不同的行为,使用了错误的一个会导致性能和正确性损失。这两个问题,有哪一个都不好,但是如果非要选择一个的话,“慢点的,正确的”程序比“快点的,易出错的”程序好。基于这个原因,你最好优先选择使用运行时常量而不是编译时常量。编译时常量比运行时常量稍微快一点儿,但是在灵活性方面则很欠缺。为了对付性能非常关键而且常量的值永远不会随着时间的改变而改变的情况,我们保留了编译时常量。

You declare runtime constants with the readonly keyword. Compile-time constants are declared with the const keyword:

readonly关键字声明运行时常量, const关键字声明编译时常量。

  1. // 编译时常量:
  2. public const int  Millennium = 2000;
  3. // 运行时常量:
  4. public static readonly int  ThisYear = 2004;

The differences in the behavior of compile-time and runtime constants follow from how they are accessed. A compile-time constant is replaced with the value of that constant in your object code. This construct:

编译时常量与运行时常量在行为上的区别源于它们是如何被访问的。编译时常量在编译后的代码中被它所表示的常量值所替换。下面的结构:

  1. if ( myDateTime.Year == Millennium )

compiles to the same IL as if you had written this:

和下面的代码比较,两者编译后的IL是一样的

  1. if ( myDateTime.Year == 2000 )

Runtime constants are evaluated at runtime. The IL generated when you reference a read-only constant references the readonly variable, not the value.

运行时常量则是在运行时被求值。当你引用一个只读的常量时,生成的IL将引用这个标记为readonly的变量,而不是它的值。

This distinction places several restrictions on when you are allowed to use either type of constant. Compile-time constants can be used only for primitive types (built-in integral and floating-point types), enums, or strings. These are the only types that enable you to assign meaningful constant values in initializers. These primitive types are the only ones that can be replaced with literal values in the compiler-generated IL. The following construct does not compile. You cannot initialize a compile-time constant using the new operator, even when the type being initialized is a value type:

当你被允许使用任何一种常量时,这种差别会带来一些约束。编译时常量只能用于基元类型(内建的整型和浮点型)、枚举、字符串。只有这些类型才允许你在初始化器(initializers声明的同时进行初始赋值)中指定有意义的常量值。只有这些基元类型才可以在编译器生成的IL中被有意义的字面值所替换。下面的创建过程将不会被编译。即使被初始化的类型是个值类型,也不能使用new操作符来初始化一个编译时常量:

  1. //不会通过编译,请使用readonly替换:
  2. private const DateTime  classCreation = new  DateTime( 2000, 1, 1, 0, 0, 0 );

Compile-time constants are limited to numbers and strings. Read-only values are also constants, in that they cannot be modified after the constructor has executed. But read-only values are different, in that they are assigned at runtime. You have much more flexibility in working with runtime constants. For one thing, runtime constants can be any type. You must initialize them in a constructor, or you can use an initializer. You can make readonly values of the DateTime structures; you cannot create DateTime values with const.

编译时常量被限制于数字和字符串。只读的值也是常量,因为它们在构造函数被执行之后就不能被修改了。但是只读的值是不同的,因为它们是在运行时被赋值的。使用运行时常量可以获得更多的灵活性。首先呢,运行时常量可以是任何类型。你必须在构造函数里面或者初始化器中初始化它们。可以为DateTime类型指定readonly的实例变量,而不能指定const的实例变量。

You can use readonly values for instance constants, storing different values for each instance of a class type. Compile-time constants are, by definition, static constants.

可以在实例常量上使用readonly,从而为类的每个具体实例存储不同的值。编译时常量则被定义为static常量。

The most important distinction is that readonly values are resolved at runtime. The IL generated when you reference a readonly constant references the readonly variable, not the value. This difference has far-reaching implications on maintenance over time. Compile-time constants generate the same IL as though you've used the numeric constants in your code, even across assemblies: A constant in one assembly is still replaced with the value when used in another assembly.

最重要的区别就是,readonly的值是在运行时被解析得到的。当你引用一个readonly常量时,生成的IL将引用这个readonly的变量,而不是它的值。这点不同,使得在一段时间之后的维护方面具有深远的意义。编译时常量编译后生成的IL,与在代码中使用数字生成的IL一样,甚至在不同的程序集之间也是这样:一个程序集里面的常量,当它在其他程序集里面被使用时,仍然会被替换成实际的数值。

The way in which compile-time and runtime constants are evaluated affects runtime compatibility. Suppose you have defined both const and readonly fields in an assembly named Infrastructure:

编译时常量和运行时常量如何被赋值,影响运行时的兼容性。假设你在Infrastructure程序集中已经同时定义了constreadonly字段:

  1.     public class UsefulValues
  2.     {
  3.         public static readonly Int32 StartValue = 5;
  4.         public const Int32 EndValue = 10;
  5.     }

In another assembly, you reference these values:

在另外的一个程序集中引用这些值:

  1.   for (Int32 i = UsefulValues.StartValue; i < UsefulValues.EndValue; i++)
  2.   {
  3.       Console.WriteLine("Value is {0}", i);
  4.   }

If you run your little test, you see the following obvious output:

如果运行上面的测试,可以看到下列明显的输出:

Value is 5

Value is 6

Value is 9

Time passes, and you release a new version of the Infrastructure assembly with the following changes:

时光飞逝,你发布了一个Infrastructure程序集的新版本,做了以下的修改:

  1.     public class UsefulValues
  2.     {
  3.         public static readonly Int32 StartValue = 105;
  4.         public const Int32 EndValue = 120;
  5.    }

You distribute the Infrastructure assembly without rebuilding your Application assembly. You expect to get this:

在没有重新编译整个应用程序集的情况下,你发布了Infrastructure程序集,并且希望得到:

Value is 105

Value is 106

Value is 119

In fact, you get no output at all. The loop now uses the value 105 for its start and 10 for its end condition. The C# compiler placed the const value of 10 into the Application assembly instead of a reference to the storage used by EndValue. Contrast that with the StartValue value. It was declared as readonly: It gets resolved at runtime. Therefore, the Application assembly makes use of the new value without even recompiling the Application assembly; simply installing an updated version of the Infrastructure assembly is enough to change the behavior of all clients using that value. Updating the value of a public constant should be viewed as an interface change. You must recompile all code that references that constant. Updating the value of a read-only constant is an implementation change; it is binary compatible with existing client code. Examining the MSIL for the previous loop shows you exactly why this happens:

事实上,是得不到任何输出的。在现在的循环中起始值是105,结束条件是10C#编译器在应用程序集中使用10替换了const常量,而不是对EndValue所使用的存储区的引用。和StartValue做个比较吧,它被声明为了readonly:在运行时被解析。因此,应用程序集在没有重新编译的情况下,就使用了新的值;简单的安装一个Infrastructure程序集的更新版本,就足够使所有使用该值的客户改变行为。对公共常量值的更新,应该被看做是接口的改变,你应该重新编译所有使用该常量的代码。而对只读常量的更新是实现上的改变,与已经存在的客户代码是二进制代码上兼容的。检查前面循环的MSIL代码会展现给你,为什么会这样:

 

  1.   IL_0001:  ldsfld     int32 Namespace.UsefulValues::StartValue
  2.   IL_0006:  stloc.0
  3.   IL_0007:  br.s       IL_0020
  4.   IL_0009:  nop
  5.   IL_000a:  ldstr      "Value is {0}"
  6.   IL_000f:  ldloc.0
  7.   IL_0010:  box        [mscorlib]System.Int32
  8.   IL_0015:  call       void [mscorlib]System.Console::WriteLine(string,
  9.                                                                 object)
  10.   IL_001a:  nop
  11.   IL_001b:  nop
  12.   IL_001c:  ldloc.0
  13.   IL_001d:  ldc.i4.1
  14.   IL_001e:  add
  15.   IL_001f:  stloc.0
  16.   IL_0020:  ldloc.0
  17.   IL_0021:  ldc.i4.s   10
  18.   IL_0023:  clt
  19.   IL_0025:  stloc.1
  20.   IL_0026:  ldloc.1
  21.   IL_0027:  brtrue.s   IL_0009

You can see that the StartValue is loaded dynamically at the top of the MSIL listing. But the end condition, at the end of the MSIL, is hard-coded at 10.

可以看出在MSIL列表的顶部,StartValue是被动态加载的。在MSIL的底部,结束的条件,被硬编码为10

On the other hand, sometimes you really mean for a value to be determined at compile time. For example, consider a set of constants to mark different versions of an object in its serialized form (see Item 25). Persistent values that mark specific versions should be compile-time constants; they never change. Thecurrent version should be a runtime constant, changing with each release.

从另一方面讲,有时确实希望一个值在编译时被决定。例如,使用一系列的常量来标记一个对象的序列化形式的不同版本。标示指定版本的持久性的值应该是编译时常量;他们从不改变。当前的版本应该是一个运行时常量,每次发布版本时都应有变化。

      

  1.  private const Int32 VERSION_1_0 = 0x0100;
  2.         private const Int32 VERSION_1_1 = 0x0101;
  3.         private const Int32 VERSION_1_2 = 0x0102;
  4.         //major release
  5.         private const Int32 VERSION_2_0 = 0x0200;
  6.         //chece for the current version:
  7.     private static readonly Int32 CURRENT_VERSION = VERSION_2_0;

You use the runtime version to store the current version in each saved file:

使用运行时版本在每个保存下来的文件中来存储当前的版本:

   

  1.     private const Int32 VERSION_1_0 = 0x0100;
  2.         private const Int32 VERSION_1_1 = 0x0101;
  3.         private const Int32 VERSION_1_2 = 0x0102;
  4.         //major release
  5.         private const Int32 VERSION_2_0 = 0x0200;
  6.         //chece for the current version:
  7.         private static readonly Int32 CURRENT_VERSION = VERSION_2_0;
  8.         //read from persistent storage,check
  9.         //stored version against compile-time constant
  10.         protected MyType(SerializationInfo info, StreamingContext cntxt)
  11.         {
  12.             Int32 storedVersion = info.GetInt32("VERSION");
  13.             switch (storedVersion)
  14.             {
  15.                 case VERSION_2_0:
  16.                     readVersion2(info, cntxt);
  17.                     break;
  18.                 case VERSION_1_1:
  19.                     readVersion1Dot1(info, cntxt);
  20.                     break;
  21.                 //etc
  22.             }
  23.         }
  24.         //write the current version
  25.         [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter == true)]
  26. void ISerializable.GetObjectData(SerializationInfo inf, StreamingContext cxt)
  27.         {
  28.             //use runtime constant for current version:
  29.             inf.AddValue("VERSION", CURRENT_VERSION);
  30.             //etc
  31.       }

The final advantage of using const over readonly is performance: Known constant values can generate slightly more efficient code than the variable accesses necessary for readonly values. However, any gains are slight and should be weighed against the decreased flexibility. Be sure to profile performance differences before giving up the flexibility. 使用const而不是readonly的最后一点优势就是性能:使用已知常量值的代码效率要比访问readonly值的代码效率稍好一点。然而,任何收获都是微小的,都应该和失去的灵活性进行衡量。在放弃灵活性之前,一定要对性能的差异进行评估。

const must be used when the value must be available at compile times: attribute parameters and enum definitions, and those rare times when you mean to define a value that does not change from release to release. For everything else, prefer the increased flexibility of readonly constants.

在一个值当编译时就必须有效的情况下,应该使用const:特性参数,枚举定义,还有非常罕见的时候,就是你想定义一个不随着每次发布而改变的值。在其他情况下,优先使用readonly常量,以获得灵活性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值