c#有两种不同版本的常量:编译时常量和运行时常量。它们有完全不同的行为,如果用的不好将花费额外性能甚至出错。如果你一定要选择其一,一个慢但正确的程序总比一个快的错的程序好,所以你应该选择运行时常量而不是编译时常量。编译时常量相对运行时常量虽然快,但并不灵活。当涉及程序性能并且其值不会改变时我们应该保留编译时常量。
定义运行时常量用关键字readonly ,编译时常量用关键字const 声明:
当你使用两者其一时,这些区别就会在一些限制上表现出来。编译时常量只能被用于基本类型,枚举,字符串。这些是你可以在初始化时指定的常量的唯一的类型。唯有这些基本类型才能在编译IL代码时直接用其字面值替代。下面这段代码不能通过编译,你不能用new操作符初始化一个编译时常量,甚至其类型是值类型:
只读变量可以是静态的也可以不是,但编译时常量从其定义只能是静态(static)的。
比较重要的区别是只读变量的值是在运行时指定。当你引用只读变量IL代码引用的是其只读变量而非其值。这种区别表现在于其维护性上。编译时常量产生同样的IL代码如同在你代码中使用数字型常量,甚至跨程序集时也这样:一个程序集里的常量在另一程序集中仍保留同样的值。
编译时常量和运行时常量赋值方式不同可能影响运行时的兼容性。假定你在程序集Infrastructrue中定义了const和readonly常量字段:
另一方面,有时你也要用编译时常量。例如,考虑一个对象的不同版本的情况,你应用编译时常量对应不同的版本,因为它们决不会改变。而现有版本则应该使用运行时常量,因为每次发布后版本号不一样:
const常量用在编译时必须确定值的情况:属性参数,枚举定义和那些不会随着版本发布而改变值的常量。无论怎样,强烈建议你使用灵活性强的运行时常量。
定义运行时常量用关键字readonly ,编译时常量用关键字const 声明:
// Compile time constant:
public const int _Millennium = 2000;
// Runtime constant:
public static readonly int _ThisYear = 2004;
编译时常量与运行时常量的行为区别是从怎样访问它们得出的。在你的目标代码中编译时常量替换为其值,下面的代码: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( 2000, 1, 1, 0, 0, 0 );
编译时常量仅局限于数学型及字符串型。只读变量也是常量,它们不能在其构造函数执行完后改变。但只读变量的值是可以改变的,因为其值是在运行时指定的。运用运行时常量有更大的灵活性,而且它可以是任何类型。你必须在构造函数中初始化它们,或可以用初始化函数。你可以创建一个DateTime结构类型的只读变量,但不能创建一个const常量。private const DateTime _classCreation = new
DateTime( 2000, 1, 1, 0, 0, 0 );
只读变量可以是静态的也可以不是,但编译时常量从其定义只能是静态(static)的。
比较重要的区别是只读变量的值是在运行时指定。当你引用只读变量IL代码引用的是其只读变量而非其值。这种区别表现在于其维护性上。编译时常量产生同样的IL代码如同在你代码中使用数字型常量,甚至跨程序集时也这样:一个程序集里的常量在另一程序集中仍保留同样的值。
编译时常量和运行时常量赋值方式不同可能影响运行时的兼容性。假定你在程序集Infrastructrue中定义了const和readonly常量字段:
public class UsefulValues
{
public static readonly int StartValue = 5;
public const int EndValue = 10;
}
在另一程序集中,你引用了它们:{
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 );
你可以看到明显的结果:i < UsefulValues.EndValue;
i++ )
Console.WriteLine( "value is {0}", i );
Value is 105
Value is 106
Value is 119
随着时间过去,你有了一个程序集Infrastructrue新的版本:Value is 106
Value is 119
public class UsefulValues
{
public static readonly int StartValue = 105;
public const int EndValue = 120;
}
你在没有重新生成程序集的情况下发布你的新程序集,你期望得到下面的值:{
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代码你会发现为什么会这样:Value is 106
Value is 119
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值是被硬编码的。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
另一方面,有时你也要用编译时常量。例如,考虑一个对象的不同版本的情况,你应用编译时常量对应不同的版本,因为它们决不会改变。而现有版本则应该使用运行时常量,因为每次发布后版本号不一样:
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;
你对每个保存的文件使用运行时常量值: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常量可以更快捷有效,但你必须权衡性能及灵活性,在你放弃灵活性前你得确信剖析过性能。// 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常量用在编译时必须确定值的情况:属性参数,枚举定义和那些不会随着版本发布而改变值的常量。无论怎样,强烈建议你使用灵活性强的运行时常量。