Effective C# 原則2:為你的常量選擇readonly而不是const(譯)

來源:互聯網
上載者:User

第二條:為你的常量選擇readonly而不是const(譯)

對於常量,C#裡有兩個不同的版本:運行時常量和編譯時間常量。
因為他們有不同的表現行為,所以當你使用不當時,將會損傷程式效能或者出現錯誤。
兩害相權取其輕,當我們不得不選擇一個的時候,我們寧可選擇一個運行慢一點但正確的那一個,而不是運行快一點但有錯誤的那個。基於這個理由,你應該選擇運行時常量而不是編譯時間常量(譯註:這裡隱藏的說明了編譯時間常量效率更高,但可能會有錯誤)。
編譯時間常量更快更直接,但在可維護性上遠不及運行時常量。保留編譯時間常量是為了滿足那些對效能要求克刻,且隨著程式已耗用時間的過去,其值永遠不發生改變的常量使用的(譯註:這說明編譯時間常量是可以不被C#採用的,但考慮到效能問題,還是做了保留)。
你可以用關鍵字readonly來聲明(declare)一個運行時常量,編譯時間常量是用關鍵字const聲明的。
//Compile time constant:
public cocnst int _Millennium = 2000;
//Runtime constant:
public static readonly int _ThisYear = 2007;//(譯註:原文為2004)
編譯時間常量與運行時常量不同之處表現在如何對他們的訪問上。
一個編譯時間常量會被目標代碼中的值直接取代。下面的代碼:
if(myDateTime.Year == _Millennium)
會與下面寫的代碼編譯成完全相同的IL代碼:
if(myDateTime.Year == 2000)

運行時常量的值是在運行時確定的。當你引用一個唯讀常量時(read-only)IL會為你引用一個運行時常量的變數,而不是直接使用該值。
當你任意的使用其中一個常量時,這些區別就在一些限制上表現出來。編譯時間常量只能是基本類型(primitive types)(built-in integral and floating-poing types),枚舉或者是字串。這些就是你只能給運行時常量在初始化時賦值的類型。這些基本類就是可以被編譯器在編譯IL代碼時直接用真實的值所取代的資料類型。下面的代碼塊(construct)不能通過編譯。你不能用new運算子初始化一個編譯時間常量,即使這個資料類型是實值型別。
//Does not complie, use readonly instead:
private const DateTime _classCreation = new DateTime(2000,1,1,0,0,0);
(譯註:DateTime是一個實值型別資料,但上面的代碼因為用了new運算子,編譯器無法在編譯確定具體的對象應該用什麼樣的實際值來取代,所以無法通過編譯。)

編譯時間常量僅限於數字和字串。唯讀變數,也就是運行時常量,在建構函式(constructor)執行完成後它們是不以能被修改的。但唯讀變數是所有不同的,因為他們是在運行時才賦值的。當你使用運行時常量時,你有更大的延展性。有一點要注意的是,運行時常量可以是任何類型的資料。而且你必須在建構函式裡對他們初始化,或者你可以用任何一個初始化函數來完成。你可以添加一個DateTime結構的唯讀變數(--運行時常量),但你不能添加一個DateTime結構的(編譯時間)常量。

你可以把每一個執行個體(的常量)指定為唯讀,從而為每一個類的執行個體存放不同的值。與編譯時間常量不同的是,它只能是靜態。
(譯註:簡單的講,運行時常量可以是一個類的執行個體成員,也可以是一個類型的靜態成員,而編譯時間常量只能是靜態成員,因此類似:static const string m_name;的代碼是不能通過編譯的。)

唯讀資料最重要的區別是他們在運行時才確定值。當你使用唯讀變數 時,IL會為你產生一個對唯讀變數引用,而不是直接產生數值。隨著時間的推移,這個區別在(系統)維護上有深遠的潛在影響。
編譯時間常量產生的IL代碼就跟直接使用數值時產生的IL是一樣的,即使是在跨程式集時:一個程式集裡的編譯時間常量在另一個程式集會保留著同樣的值(譯註:這裡說的不是很清楚,看後面的這個例子可能會更清楚一些)。
編譯時間常量和運行時常量的賦值方法對運行時的相容性有所影響。
假設你已經在程式集Infrastructure中同時定義了一個const和一個readonly變數:
public class UserfulValues{
 public static readonly int StartValue = 5;
 public const int EndValue = 10;
}
同時,在另一個程式集(譯註:這個程式集認為是我們做測試的應用程式的程式集,下面所說的應用程式的程式集都是指的這個程式集)中,你引用了這些值:
for(int i=UserfulValues.StartValue;i<UserfulValues.EndValue;i++){
 Console.WriteLine("value is {0}",i);
}
如果你運行這個簡單測試程式,你可以看到下面明顯的結果:
value is 5
value is 6
...
value is 9
過後,你又為程式集Infrastructure發布了個新的版本,並做了如下的修改:
public class UserfulValues{
 public static readonly int StartValue = 105;
 public const int EndValue = 120;
}
你單獨的發布了程式集Infrastructure而沒有全部編譯你的程式,你希望得到下面的:
value is 105
value is 106
...
value is 119
事實上,你什麼也得不到。上面的迴圈已經是用105開始而用10來結束。C#編譯器(在編譯時間)把常量用10來代替應用程式的程式集中的使用,而不是用常量EndValue所儲存的值。而常量StartValue的值,它是被申明為唯讀,它可以在運行時重新讀取該常量的值。因此,應用程式的程式集可以在不用重新編譯的情況下使用新的資料,簡單的編譯一下Infrastructure程式集,然後重新布署安裝一下,就足夠讓你的客戶可能使用這些新的資料了。更新的編譯時間常量應該看成是介面的變化。你必須重新編譯所有引用到編譯時間常量的代碼。更新的運行時常量則可以當成是實現的改變,這於在用戶端已經存在的二進位代碼是相容的。用MSIL解釋一下前面的那個迴圈裡發生了什麼:
IL_0000: ldsfld int32 Chapter1.UserfulValues::StartValue
IL_0005: stloc.0
IL_0006: br.s IL_001c
IL_0008: ldstr "value is {0}"
IL_000d: ldloc.0
IL_000e: box [mscrolib]System.Int32
IL_0013: call void [mscrolib]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(的值)是動態載入的。
但是,在MSIL命令的最後,結束條件是把值10當成硬代碼(hard-coded)使用的。

另一方面,有些時候你也須要為某些值使用編譯時間常量。例如:考慮一個須要識別不同版本的續列化情形。用來標識一個特殊版本號碼的常量應該是一個編譯時間常量,它們決不會發生改變。而目前的版本號則應該是一個運行時常量,在不同的版本發布後會有所改變。
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;
//Chech for the current version:
private static readonly int CURRENT_VERSION = VERSION_2_0;
在每次存檔時,你用運行常量來儲存目前的版本號。
//Read fom persistent storage, check stored version against complie-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:
  readVersion1(info,cntxt);
  break;
 //etc.  
 }
}

//Write the current version:
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo inf,StreamingContext cxt){
 //use runtime constant for currnet version
 inf.AddValue("VERSION",CURRENT_VERSION);
 //
 //write remaining delements...
}

最後一個有利的原因而使我們要使用編譯時間常量,就是它的效能。比起運行時常量,已知的編譯時間常量可以更直接有效被訪問。然而,效能上的功效是甚微的,並且應該與延展性的降低進行一個權衡。Be sure to profile performace differences before giveing up the flexibility.

const的值必須在編譯時間被確定,(它們可以是):屬性參數,枚舉定義,以及一小部份你認為應該定義一個值且該值不能在不同的版本發布時發生改變的常量。
無論如何,寧願選擇伸縮性更強的運行時常量。

============================
小結:第一次翻譯。昨天花了半天時間,今天又花了幾個小時。終於完成了Item2的翻譯。感覺很吃力。或者我的翻譯很糟糕,或者已經有更好的翻譯了,但不管怎樣,這也算是我自己辛苦勞動的結果。希望對讀者有所協助。本想在翻譯中添加一我自己的看法,那樣的話對原文的改動可能比較大,所以沒做太多註解。我會繼續努力翻譯完後面的Items,不管翻譯的怎樣,也不管是不是有其它的翻譯已經完成了。這對自己也是一個學習過程。我會在後面的翻譯中一些我自己的看法,當然儘可能的保留原文的樣子,實在不好辦的,我就會以讀書筆記的形式另外寫文章。
另外,我也不知道這樣的文章能不能發在首頁,如果不行,以後的就都發到其它區吧。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.