1 // Lib.dll
2 public class Lib
3 {
4 public const string VERSION = "1.0";
5 public static void PrintVersion(string version = "1.0")
6 {
7 Console.WriteLine(version);
8 }
9 }
然後有這麼個調用方:
// Program.exe
class Program
{
static void Main()
{
Console.WriteLine(Lib.VERSION);
Lib.PrintVersion();
Console.Read();
}
}
Program.exe的運行結果是顯而易見的: 1.0 1.0 過了一段時間,Lib更新版本了:
// Lib.dll
public class Lib
{
public const string VERSION = "2.0";
public static void PrintVersion(string version = "2.0")
{
Console.WriteLine(version);
}
}
把Lib.dll重新編譯確保Program.exe引用了最新的DLL,然後再運行Program.exe,結果: 1.0 1.0 重新編譯Program.exe以後再次運行: 2.0 2.0 發現問題了吧,調用方必須重新編譯才能確保可選參數和常量的值是最新的。 原因是這樣的:
1 .method private hidebysig static
2 void Main () cil managed
3 {
4 // Method begins at RVA 0x2050
5 // Code size 30 (0x1e)
6 .maxstack 8
7 .entrypoint
8
9 IL_0000: ldstr "1.0"
10 IL_0005: call void [mscorlib]System.Console::WriteLine(string)
11 IL_000a: ldstr "1.0"
12 IL_000f: call void CsConsole.Program/Lib::PrintVersion(string)
13 IL_0014: call int32 [mscorlib]System.Console::Read()
14 IL_0019: pop
15 IL_001a: ret
16 }
這是第一次編譯之後Program.Main方法的IL,Lib.VERSION完全被編譯成了字面量"1.0"(第10行),而第11行的"1.0"是來自(第一次編譯時的)Lib.dll的元數據。 顯然,不管怎麼更新Lib的代碼,只要不重新編譯Program,這裡的兩個值就沒辦法得到更新,而實際的生產環境中經常沒法保證調用方會被重新編譯。 至於為什麼要這樣編譯,我是這麼理解的。 常量的值是在常量池裡待著的,通過類的成員去取值顯然是不如直接從常量池取來得方便快捷。 而可選參數的實現方式,則是在編譯時提前進行判斷與賦值,節省了運行時的時間。 解決方法: 對於可選參數,《CLR via C#》建議的方法是這樣的:
1 public static void PrintVersion(string version = null)
2 {
3 if (version == null)
4 {
5 version = "1.0";
6 }
7 Console.WriteLine(version);
8 }
很顯然這段代碼的行為與之前相比是有變化的,但是大多數情況下確實可以解決值更新的問題,但是也帶來了運行時效率的問題。 對於常量,從我使用的示例就可以看出來,版本號這類的值不應該定義為常量,使用readonly可以達到目的。 而對於真正的常量,則不應該輕易地變更它的值。 最後嘛,Java雖然沒有const,但是static final也有同樣的表現,同樣需要注意這一點。