常量
常量是值從不變化的符號。定義常量符號時,它的值必須能在編譯時確定。確定後,編譯器將常量值儲存到程式集後設資料中。這意味著只能定義編譯器識別的基元型別的常量。在C#中,可用於定義常量:Boolean,Char,Byte,SByte,Int16,UInt16,Int32,UInt32,Int64,UInt64,Single,Double,Decimal,String。C#也允許定義非基元的常量變數,前提是把值設為null:
public class Program
{
public const Program program = null;
}
程式碼引用常量符號時,編譯器在定義常量的程式集的後設資料中查詢該符號,提取常量的值,將值嵌入生成的IL程式碼中。由於常量的值直接嵌入生成的IL程式碼中,所以在執行時不需要為常量分配任何記憶體。除此之外,不能獲取常量的地址,也不能以傳引用的方式傳遞常量。
這也意味著沒法做到很好的版本控制。例如我們首先編譯一個dll,這個程式集記憶體在一個常量:
public sealed class SomeLibraryType
{
public const int MAX = 50;
}
接下來用以下程式碼生成一個應用程式程式集:
public class Program
{
static void Main(string[] args)
{
Console.WriteLine(SomeLibraryType.MAX);
}
}
編譯器在生成程式碼時,會注意到MAX是值為50的常量符號,所以會將int值50嵌入應用程式的IL程式碼。事實上,在生成應用程式程式集後,執行根本不會載入DLL程式集,可以把它從磁碟上刪除。
這個例子清楚的展示了版本控制問題。如果開發人員將常量MAX更改為1000,且只是重新生成dll,那麼應用程式程式集不會有任何影響,依然輸出50。想要輸出新值,則需要重新編譯。
欄位
欄位是一種資料型別,其中容納了一個值型別的例項或者對一個引用型別的引用。CLR支援型別(靜態)欄位和例項(靜態)欄位。如果是型別欄位,容納欄位資料所需的動態記憶體是在型別物件中分配的,而型別物件是在型別載入到一個AppDomain時建立的。將型別載入到一個AppDomain的時機通常是在引用了該型別的任何方法首次進行JIT編譯的時候。如果是例項欄位,容納欄位資料所需的動態記憶體是在構造型別的例項時分配的。
CLR支援readonly欄位和read/write欄位。大多數字段都是read/write欄位,意味著在程式碼執行過程中,欄位值可以多次改變。但readonly欄位只能在構造器方法中寫入(構造器方法只會在物件生成時,呼叫一次)。編譯器和驗證機制確保readonly欄位不會被構造器以外的任何方法寫入。不過我們仍然可以透過反射的方式修改readonly欄位。
修改下之前的程式碼,舉個例子,將SomeLibraryType型別更改如下,重新生成dll:
public sealed class SomeLibraryType
{
public static readonly int MAX = 50;
}
然後我們重新編譯應用程式程式集,會輸出50。我們將SomeLibraryType.MAX改為1000,然後重新編譯dll,會發現能夠輸出1000了。使用ildasm.exe檢視下IL程式碼,可以看到,它會去對應程式集中找MAX值:
以下表格稍微介紹一下欄位修飾符
CLR術語 | C#術語 | 說明 |
---|---|---|
Static | static | 這種欄位是型別狀態的一部分,而不是物件狀態的一部分 |
Instance | 預設 | 這種欄位與型別的一個例項關聯,而不是與型別本身關聯 |
InitOnly | readonly | 這種欄位只能由一個構造器方法中的程式碼寫入 |
Volatile | volatile | 編譯器,CLR和硬體不會對訪問這種欄位的程式碼執行“執行緒不安全”的最佳化措施。只有以下型別能標記為volatile:所有引用型別,Single,Boolean,Byte,SByte,Int16,UInt16,Int32,UInt32,Char,以及基礎型別為Byte,SByte,Int16,UInt16,Int32,UInt32的所有列舉型別 |