透過IL看C# (外一篇)
警惕常量陷阱
原文地址:http://www.cnblogs.com/AndersLiu/archive/2008/11/23/csharp-via-il-constant-a.html
原創:Anders Liu
摘要:常量的含義本是“永遠不會變的量”,但是如果作為類庫開發人員,把常量用作“可以由我變,但不能由你變”的量,那就可能鑄成大錯了。
下面是老劉寫的一個類庫中的一個類:
代碼1 - 老劉的“類庫”
namespace AndersLiu.CSharpViaIL.Constant_Library<br />{<br />public class Library<br />{<br />public const int Version = 1;</p><p>public string GetVersion()<br />{<br />return string.Format("You are currently using version {0}.", Version);<br />}<br />}<br />}
其中的常量Version表示類庫的目前的版本,而方法GetVersion會給出描述目前的版本的字串。用下面的命令列可以將其編譯為一個DLL:
csc /t:library Library.cs
接下來,一個倒黴的傢伙從老劉這買了這個類庫,寫了自己的程式:
代碼2 - 老劉的“消費者”
namespace AndersLiu.CSharpViaIL.Constant_Program<br />{<br />using System;<br />using AndersLiu.CSharpViaIL.Constant_Library;</p><p>class Program<br />{<br />static void Main()<br />{<br />Console.WriteLine("Library Version: {0}", Library.Version);</p><p>Library lib = new Library();<br />string verstr = lib.GetVersion();<br />Console.WriteLine(verstr);<br />}<br />}<br />}
這個傢伙用下面的命令列編譯了自己的程式:
csc /r:Library.dll Program.cs
運行程式,一切正常,螢幕顯示出預期的結果:
Library Version: 1
You are currently using version 1.
過了兩天,老劉升級代碼了。都升級哪些部分了呢?把Version的值改成2了,然後重新編譯了Library。所以代碼就不貼了。前面這個傢伙因為很乖,從老劉這裡買了正版,所以老劉承諾免費升級50次,因此他拿到了新版的Library.dll。
當這個傢伙再跑自己的程式(注意他沒有重新編譯自己的代碼),問題來了:
Library Version: 1
You are currently using version 2.
我們看到,通過常量訪問得到的版本依然是1,而通過類庫方法得到的版本字串是2。
這是怎麼回事呢?讓我們祭出ILDasm,看一下Version常量的定義:
代碼3 - IL中的常量定義
.field public static literal int32 Version = int32(0x00000002)
Version定義中的關鍵字literal表明,這是一個字面常量值。“字面”意味著其值將被直接編譯到IL代碼中,而不會保留對這個常量的引用。
因此,當我們看到Program.cs中Main方法的IL代碼後,就不那麼吃驚了:
代碼4 - 引用Version常量部分的IL代碼
IL_0001: ldstr "Library Version: {0}"<br />IL_0006: ldc.i4.1<br />IL_0007: box [mscorlib]System.Int32<br />IL_000c: call void [mscorlib]System.Console::WriteLine(string,<br /> object)<br />
我們可以看到,這雷根本沒有Version這個常量的影子。只有IL_0006: ldc.i4.1這一行直接載入了數值1——在編譯Program的時候,類庫中Version常量的值。
有朋友可能想說,那重新編譯一下Program不就解決問題了?可問題在於,.NET的一個重要原則就是部署容易和消除DLL陷阱。不能強制要求客戶在類庫升級後還要重新編譯自己的程式。
所以,對於類庫設計人員來說,當暴露一個公開的常量時要非常小心,只有確信一個值永遠不會發生變化(包括今後的一系列升級)時,才能使用常量。否則請使用唯讀(只有get訪問器)屬性或唯讀(readonly)欄位來返回這樣的值。
返回目錄:透過IL看C#