類型基礎
全部類型都從System.Object派生
CLR要求全部對象都用new 操做符來建立。
Employee e = new Employee("Constructor Parameters");
如下是
new 操做符所作的事情:
#1, 計算類型及全部基類型(一直到System.Object, 雖然它沒有定義本身的實例字段)中定義的全部實例字段須要的字節數。
堆上的每一個對象還須要一些額外(overhead 開銷成員)的成員 -- 即「類型對象指針」(type object pointer)和「同步塊索引」(sync block index)。這些成員由CLR用於管理對象。這些額外成員的字節數會計入對象大小。
#2, 它從託管堆中分配指定類型要求的字節數,從而分配對象的內存,分配的全部字節都設爲0。
#3, 它初始化對象的「類型對象指針」和「同步塊索引」成員。
#4, 調用類型的實例構造器,向其傳入在對new 的調用中指定的任何實參。大多數編譯器都在構造器中自動生成代碼來調用一個基類構造器。每一個類型的構造器在調用時,都要負責初始化由這個類型定義的實例字段。最總調用的是System.Object的構造器,該構造器只是簡單地返回,不會作其餘任何事情。
new執行了全部操做以後,會返回指向新建對象一個引用(或指針)。在前面的示例代碼中,這個引用會保存到變量e中,後者具備Employee類型。
類的「新實例」和「實例成員」:兩種不一樣的「實例」。一種是類的實例,也就是具體的對象。另外一種是類中定義的實例字段。所謂「實例字段」,就是指非靜態字段,有時也稱爲「實例成員」。簡單地說,實例成員是屬於類的對象的,
而靜態成員是屬於類的。
類型安全
CLR老是知道一個對象(某個類型的實例)是什麼類型。全部表達式都解析成某個類型的實例,在編譯器生成的代碼中,只會執行對這個類型來講有效的操做。與非類型安全的語言相比,類型安全的語言的優點在於:程序員會犯的許多錯誤能在編譯時檢測到,確保代碼在你的嘗試執行它以前是正確的。除此以外,編譯時語言一般能生成更小、更快的代碼,由於他們能在編譯時進行更多的假設,並在生成的IL和元數據中落實那些假設。
類型轉換
CLR最重要的特性之一就是類型安全性。在運行時,CLR老是知道一個對象是什麼類型。調用GetType() 方法,老是知道一個對象確切的類型是什麼。
開發中,開發人員會常常將一個對象從一種類型轉換爲其餘各類類型。CLR容許將一個對象轉換爲它的(實際)類型或者它的任何基類型。
#1: 向基類型的轉換被認爲是一種安全的隱式轉換。
#2: 將對象轉換爲它的某個派生類型時,C#要求只能進行顯式轉換,由於這樣的轉換有可能在運行時失敗。在運行時,CLR檢查類型操做,肯定老是轉換爲對象的實際類型或者它的任何基類型。
這就是類型安全的設計。若是CLR容許這樣的轉型,就無類型安全性可言了,將出現難以預料的結果 -- 其中包括應用程序崩潰,以及安全漏洞的出現(由於一種類型能輕鬆地假裝成另外一種類型)。
類型假裝是許多安全漏洞的根源,它還會破壞應用程序的穩定性和健壯性。類型安全是CLR一個重要的目標。
is 操做符,檢查一個對象是否兼容於指定的類型,並返回一個Boolean值;true或false。注意 is 操做符永遠不會拋出異常。若是對象引用是null,is操做符老是返回false。
Object o = new Object();
if (o is Employee)
{
Employee e = (Employee)o;
}
這段代碼中,CLR實際上會檢查兩次對象的類型。
CLR 的類型檢查加強了安全性,但無疑也會對性能形成必定影響。由於CLR首先必須判斷變量引用的對象的實際類型。而後,CLR必須遍歷繼承層次結構,用每一個基類型去核對指定的類型(如Employee)。
上面這個事一個至關經常使用的編程模式,因此C#專門提供了as操做符,目的就是簡化這種代碼的寫法,同時提高性能。
Employee e = o as Employee;
if(e != null)
{
//.....
}
as 操做符的工做方式與強制類型轉換同樣,只是它永遠不會拋出一個異常 -- 相反,若是對象不能轉型,結果就是null。因此,正確作法也就是檢查最終生成的引用是否爲null。應該不要直接使用最終生成的引用,不然可能會拋出一個System.NullReferenceException 異常。
注意:C#容許在一個類型中定義轉換操做符方法。只有在使用一個轉型表達式時,纔會調用這些方法;使用C#的as或者is操做符時,永遠不會調用他們。
命名空間 (namespace)
用於對相關的類型進行邏輯性分組,開發人員可使用命名空間方便地定位一個類型。例如,System.Text命名空間定義了一組執行字符串處理的類型。
using 指令指示編譯器爲每個類型附加不一樣的前綴,直到找到一個匹配項。using的使用,不只極大地減小打字量,還有助於加強代碼的可讀性。
using指令還支持另外一種形式,容許爲一個類型或者命名空間建立別名。若是隻想使用一個命名空間中的少數幾個類型,不但願它的全部類型都跑出來「污染」全局命名空間,別名就顯得十分方便。
using System;
using jack = CSI.Widget;
重要提示:CLR並不知道命名空間的任何事情。訪問一個類型時,CLR須要直到類型的完整名稱(多是一個至關長的、包含句點符號的名稱)以及該類型的定義具體在哪個程序集中。這樣一來,「運行時」才能加載正確的程序集,找到目標類型,並對其進行操做。
編譯器會掃描引用的全部程序集,在其中查找類型的定義。一旦找到正確的程序集,程序集信息和類型信息就會嵌入最終生成的託管模塊的元數據中。爲了獲取程序集信息,必須將定義了「引用的類型」的程序集傳給編譯器。
默認狀況下,C#編譯器會自動在MSCorLib.dll 程序集中查找 「引用的類型」,即便你沒有顯式告訴它這樣作。MSCorLib.dll程序集中包含了全部核心Framework類庫(FCL)類型的定義,好比Object, Int32, String等。
命名空間和程序集(實現了一個類型的文件)不必定是相關的。特別是,同一個命名空間中的各個類型多是在不一樣的程序集中實現的。在一個程序集中,也可能包含不一樣命名空間中的類型。
運行時的相互關係
類型、對象、線程棧和託管堆在運行時的相互關係。調用靜態方法、實例方法和虛方法的區別。
已經加載了CLR的一個Microsoft Windows 進程。這個進程中,可能存在多個線程。一個線程的建立時,會分配到一個1MB大小的棧。這個棧的空間用於向方法傳遞實參,並用於方法內部定義的局部變量。棧是從高位內存地址向低位內存地址構建的。
棧幀(stack frame)表明的是當前線程的調用棧中的一個方法調用。在執行線程的過程當中進行的每一個方法的調用都會在調用棧中建立並壓入一個stack frame
至此咱們討論了源代碼、IL和JIT編譯的代碼之間的關係,還討論了線程棧、實參、局部變量以及這些實參和變量如何引用託管堆上的對象。我知道了,對象中包含一個指針,它指向對象的類型對象(類型對象中包含靜態字段和方發表)。
還討論了JIT編譯器如何決定靜態方法、非虛實例方法以及虛實例方法的調用方式。這一切的理解,能夠幫助深入地認識CLR的工做方式。
注意,Employee和Manager類型對象都包含「類型對象指針」成員。這是因爲類型對象本質上也是對象。CLR建立類型對象時,必須初始化這些成員。初始化成什麼呢?CLR開始在一個進程中運行時,會當即爲MSCorLib.dll中定義的System.Type類型建立一個特殊的類型對象。Employee和Manager
類型對象都是該類型的「實例」。所以,它們的類型對象指針成員會初始化成對System.Type類型對象的引用,以下面所示。
-----------------------------------------------------------