c#有两种不同版本的常量:编译时常量和运行时常量。它们有完全不同的行为,如果用的不好将花费额外性能甚至出错。如果你一定要选择其一,一个慢但正确的程序总比一个快的错的程序好,所以你应该选择运行时常量而不是编译时常量。编译时常量相对运行时常量虽然快,但并不灵活。当涉及程序性能并且其值不会改变时我们应该保留编译时常量。
   定义运行时常量用关键字readonly ,编译时常量用关键字const 声明:
// Compile time constant:
public const int _Millennium = 2000;
// Runtime constant:
public static readonly int _ThisYear = 2004;
   编译时常量与运行时常量的行为区别是从怎样访问它们得出的。在你的目标代码中编译时常量替换为其值,下面的代码:
if ( myDateTime.Year == _Millennium )
   如果你写成下面那样,那么将编译成同样的IL代码:
if ( myDateTime.Year == 2000 )
   当你引用read-only常量时,产生的IL代码引用readonly 变量而不是其值。
   当你使用两者其一时,这些区别就会在一些限制上表现出来。编译时常量只能被用于基本类型,枚举,字符串。这些是你可以在初始化时指定的常量的唯一的类型。唯有这些基本类型才能在编译IL代码时直接用其字面值替代。下面这段代码不能通过编译,你不能用new操作符初始化一个编译时常量,甚至其类型是值类型:
// Does not compile, use readonly instead:
private const DateTime _classCreation = new
  DateTime( 
200011000 );
   编译时常量仅局限于数学型及字符串型。只读变量也是常量,它们不能在其构造函数执行完后改变。但只读变量的值是可以改变的,因为其值是在运行时指定的。运用运行时常量有更大的灵活性,而且它可以是任何类型。你必须在构造函数中初始化它们,或可以用初始化函数。你可以创建一个DateTime结构类型的只读变量,但不能创建一个const常量。
   只读变量可以是静态的也可以不是,但编译时常量从其定义只能是静态(static)的。
   比较重要的区别是只读变量的值是在运行时指定。当你引用只读变量IL代码引用的是其只读变量而非其值。这种区别表现在于其维护性上。编译时常量产生同样的IL代码如同在你代码中使用数字型常量,甚至跨程序集时也这样:一个程序集里的常量在另一程序集中仍保留同样的值。
   编译时常量和运行时常量赋值方式不同可能影响运行时的兼容性。假定你在程序集Infrastructrue中定义了const和readonly常量字段:
public class UsefulValues
{
  
public static readonly int StartValue = 5;

  
public const int EndValue = 10;
}
   在另一程序集中,你引用了它们:
for ( int i = UsefulValues.StartValue;
  i 
< UsefulValues.EndValue;
  i
++ )
  Console.WriteLine( 
"value is {0}", i );
   你可以看到明显的结果:
Value is 105
Value 
is 106

Value 
is 119

   随着时间过去,你有了一个程序集Infrastructrue新的版本:
public class UsefulValues
{
  
public static readonly int StartValue = 105;

  
public const int EndValue = 120;
}
   你在没有重新生成程序集的情况下发布你的新程序集,你期望得到下面的值:
Value is 105
Value 
is 106

Value 
is 119

   实际上,并无输出。因为这个循环以105开始10结束。c#编译器将常量的值10置入应用程序集中,替代EndValue存储的值。与StartValue的值相比,它声明为只读,意味着可以在运行时取值,因此应用程序集在没有重新编译的情况下取其新值,简单的安装一下更新后的程序集就可以改变所有客户用的值。更新一个公共常量的值可以看作是接口的改变,你必须重新编译用到这个值的所有代码。更新一个只读变量你可以看作是实现的改变,它对客户代码是二进制兼容的。对上面那段循环进行验证MSIL代码你会发现为什么会这样:
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
   你会发现在MSIL代码中StartValue值是动态装载的,但是EndValue值是被硬编码的。
   另一方面,有时你也要用编译时常量。例如,考虑一个对象的不同版本的情况,你应用编译时常量对应不同的版本,因为它们决不会改变。而现有版本则应该使用运行时常量,因为每次发布后版本号不一样:
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;
   你对每个保存的文件使用运行时常量值:
// 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
}

   const常量最后一个优于readonly常量的地方就是性能:访问const常量相对readonly常量可以更快捷有效,但你必须权衡性能及灵活性,在你放弃灵活性前你得确信剖析过性能。
   const常量用在编译时必须确定值的情况:属性参数,枚举定义和那些不会随着版本发布而改变值的常量。无论怎样,强烈建议你使用灵活性强的运行时常量。