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( 2000, 1, 1, 0, 0, 0 );
編譯時間常量僅局限於數學型及字串型。唯讀變數也是常量,它們不能在其建構函式執行完後改變。但唯讀變數的值是可以改變的,因為其值是在運行時指定的。運用運行時常量有更大的靈活性,而且它可以是任何類型。你必須在建構函式中初始化它們,或可以用初始化函數。你可以建立一個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常量用在編譯時間必須確定值的情況:屬性參數,枚舉定義和那些不會隨著版本發布而改變值的常量。無論怎樣,強烈建議你使用靈活性強的運行時常量。