导航

Effiective C#:ITEM2:优先使用readonly而不是const

Posted on 2007-03-20 21:19  taoeternal  阅读(458)  评论(0编辑  收藏  举报

C#中常量有两个不同版本:编译时刻常量和运行时常量。它们的行为相去甚远,使用错误版本的常量会
损失程序性能或者出错。两个问题都是我们不愿见到的,但如果一定要选择,一个速度略慢但正确的程
序比一个快速却出错的程序要好的多。所以你应该优先选择运行时常量而不是编译时刻常量。编译时刻
常量会稍快一点,但和运行时常量相比柔韧性却差很多。只有当性能异常重要并且常量的值永远不会随
时间改变的时候才用编译时刻常量

运行时常量使用readonly关键字来声明。编译时刻常量用const关键字。

编译时刻常量被你的对象代码中的值替代

运行时常量在运行时刻确定。当引用一个[引用了readonly变量的只读常量]而不是[值]的时候

 这个差别给我们使用两中类型的常量带来几个限制。编译时刻常量只能用作简单类型(内建整型浮点型
),每局或者字符串。这些类型是你可以在初始化器重赋予有意义常量值的仅有的几种类型,也是编译
器生成的IL中能被替换为文字值的仅有类型。你不能使用new操作符初始化编译时刻常量,即便类型被初
始化为值类型

// Does not compile, use readonly instead:
private const DateTime _classCreation = new
DateTime( 2000, 1, 1, 0, 0, 0 );
readonly值夜是常量,他们不能在构造器执行之后被修改。运行时常量有更多的柔韧性。
有一点,运行时常量可以是任何类型。
你一定要在一个构造器中初始化他们,或者你可以使用一个初始化器。
 

Item 2: Prefer readonly to const

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
.

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

// Compile time constant:

public const int _Millennium = 2000;

 

// Runtime constant:

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:

if ( myDateTime.Year == _Millennium )

 

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

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.

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:

// Does not compile, use readonly instead:

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.

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.

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.

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:

public class UsefulValues

{

 public static readonly int StartValue = 5;

 

 public const int EndValue = 10;

}

 

In another assembly, you reference these values:

for ( int i = UsefulValues.StartValue;

 i < UsefulValues.EndValue;

 i++ )

 Console.WriteLine( "value is {0}", i );

 

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:

public class UsefulValues

{

 public static readonly int StartValue = 105;

 

 public const int EndValue = 120;

}

 

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

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:

IL_0000: ldsfld     int32 Chapter1.UsefulValues::StartValue

IL_0005: stloc.0

IL_0006: br.s       IL_001c

IL_0008: ldstr      "value is {0}"

IL_000d: ldloc.0

IL_000e: box        [mscorlib]System.Int32

IL_0013: call       void [mscorlib]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

 

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.

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.

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;

 

// check for the current version:

private static readonly int CURRENT_VERSION =

 VERSION_2_0;

 

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

// Read from persistent storage, check

// stored version against compile-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:

    readVersion1Dot1( info, cntxt );

    break;

 

 // etc.

 }

}

 

// Write the current version:

[ SecurityPermissionAttribute( SecurityAction.Demand,

 SerializationFormatter =true ) ]

void ISerializable.GetObjectData( SerializationInfo inf,

    StreamingContext cxt )

{

 // use runtime constant for current version:

 inf.AddValue( "VERSION", CURRENT_VERSION );

 

 // write remaining elements...

}

 

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 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.