常量

常量的值永遠不會變化,因此總是被視為靜態成員。

const int someConst = 1;

ildasm 裡可以看到它會被標記成 literal

someConst : private static literal int32

編譯期特性

  • 常量值必須能在編譯期確定,編譯器會把它直接寫入程序集元數據。
  • 因此,常量類型通常必須是編譯器能直接識別的基元類型。
  • C# 也允許非基元類型的常量,但值只能是 null

當代碼引用常量時,編譯器會去定義該常量的程序集元數據裡查值,然後把這個值直接嵌入產生出的 IL 裡。

這帶來三個結果:

  • 運行時不需要為常量另外分配內存。
  • 不能取得常量地址。
  • 不能以傳引用的方式傳遞常量。

版本控制問題

常量在版本控制上其實很不方便。假設先有一個定義常量的 DLL:

public sealed class SomeLibraryType
{
    public const int MaxEntriesInList = 1000;
}

然後有一個引用了這個常量的 EXE:

public sealed class Program
{
    public static void Main()
    {
        Console.WriteLine("Max Entries supported in list: " +
                          SomeLibraryType.MaxEntriesInList);

        Console.ReadLine();
    }
}

在生成 EXE 時,編譯器不會等到運行時再去讀 DLL 裡的值,而是直接把常量值嵌進應用程序自己的 IL 中:

IL_0000:  nop
IL_0001:  ldstr      "Max Entries supported in list: "
IL_0006:  ldc.i4     0x3e8
IL_000b:  stloc.0
IL_000c:  ldloca.s   V_0
IL_000e:  call       instance string [mscorlib]System.Int32::ToString()
IL_0013:  call       string [mscorlib]System.String::Concat(string,
string)
IL_0018:  call       void [mscorlib]System.Console::WriteLine(string)

這代表:

  • 就算你之後重新修改並重新發佈 DLL,也不會影響已經編譯好的 EXE。
  • 如果想讓應用程序吃到新的常量值,EXE 也必須重新編譯一次。

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter