CLR要求所有類型最終都要從System.Object派生。也就是所,下面的兩個定義是完全相同的,
//隱式派生自System.Objectclass Employee { .....}//顯示派生子 System.Objectclass Employee : System.Object { ..... }由于所有類型最終都是從System.Object派生的,所以可以保證每個類型的每個對象都有一組最基本的方法。
System.Object提供了如下所示的公共實例方法。
| Equals(Object) | 確定指定的對象是否等于當前對象。如果兩個對象具有相同值就返回ture. |
| GetHashCode | 返回對象的值得一個哈希碼。如果某個類型的對象要在哈希表集合中作為key使用,該類型應該重寫這個方法。方法應該為不同的對象提供一個良好的分布。 |
| ToString | 該方法默認返回類型的完整名稱(this.GetType().FullName)。 |
| GetType | 返回從Type派生的一個對象的實例,指出調用GetType的那個對象是什么類型。返回的Type類型可以與反射類配合使用,從而獲取與對象的類型相關的元數據信息。 |
System.Object的受保護方法
| MemberwiseClone | 這個非虛方法能創建類型的一個新實例,并將對象的實例字段設為與this對象的實例字段完全一致。返回的是對新實例的一個引用 |
| Finalize | 在垃圾回收器判斷對象應該被作為垃圾收集之后,在對象的內存被實際回收之前,會調用這個虛方法。需要在回收之前執行一些清理工作的類型應該重寫這個方法。 |
CLR要求所有對象都是用new操作符來創建。比如
Employee e = new Employee("ConstructorParam1");以下是new操作符所做的事情:
1)它計算類型及其所有基類型(一直到System.Object)中定義的所有實例需要的字節數。堆上的每個對象都需要一些額外的開銷成員——"類型對象指針(type object pointer)"和"同步塊索引"(sync block index)。這些成員由CLR用于管理對象。這些額外成員的字節數會計入對象大小。
2)它從托管堆中分配指定類型要求的字節數,從而分配對象的內存,分配的所有字節都設為零(0)。
3)它初始化對象的"類型對象指針"和"同步塊索引"成員。
4)調用類型的實例構造器,向其傳入對new的調用中指定的任何實參(本例中是"ConstructorParam1")。大多數編譯器都在構造器中自動生成代碼來調用一個基類的構造器。每個類型的構造器在被調用時,都要負責初始化這個類型定義的實例字段。最后調用的是System.Object的構造器,該構造器只是簡單的返回,不會做其它任何事情。
new 執行了所有的操作后,會返回執行新建對象的一個引用。在本例中,這個引用會保存到變量e中,具有Employee類型。
注意:上面提到過"類型對象指針",類型對象不是類型的對象/實例,這兩者是有區別的。
----------------------------------------------------------------------------------
CLR最重要特性之一就是類型的安全性。在運行時,CLR始終知道一個對象的類型,可以調用GetType方法,得到對象的類型。
CLR允許將一個對象轉換為它的實際類型或者它的任何基類型。
C#不要求使用特殊語法即可將一個對象轉換為它的任何及類型,因為向基類型的轉換被認為是一種安全的隱式轉換。但是,將對象轉換為它的某個派生類時,C#要求開發人員只能進行顯示轉換,因為這樣的轉換在運行時可能失敗。
public static void Main() { // 不需要轉型 Object o = new Employee(); // 需要進行強制類型轉換 Employee e = (Employee) o; }在C#語言中進行類型轉換的另一種方式是使用is操作符。is操作符檢查一個對象是否兼容指定的類型,并返回一個Boolean值(true和false)。注意,is操作符是不會返回異常信息的。
is操作符通常這樣使用:
if ( o is Employe ){ Employee e = (Employee) o; }在這段代碼中,CLR實際是會檢查兩次對象的類型。is操作符首先核實o是否兼容Employee類型。如果是,在if內部,CLR還會再次核實o是否引用一個Employee。CLR的類型檢查增強的安全性,但無疑也會對性能造成一定影響。
C#專門提供了 as 操作符,目的就是簡化這種代碼的寫法,同時提升性能。
as操作符通常這樣使用:
Employee e = o as Employee; if ( e != null ){ //在if中使用e } as操作符的工作方式與強制類型轉換一樣,只是它是不會拋出異常的,如果不能轉化,結果就是null。所以,正確的做法就是檢查最終生成的引用是否為null。如果企圖直接使用轉換后的引用,就會拋出異常。---------------------------------------------------------------------------------- 命名空間(namespace)用于對相關的類型進行邏輯分組,開發人員使用命名空間來方便的定位一個類型。命名空間和程序集不一定是相關的,也就是說它們之間沒有必然聯系。
----------------------------------------------------------------------------------
現在將解釋類型、對象、線程棧和托管堆在運行時的相互聯系。此外,還將解釋調用靜態方法、實例方法和虛方法的區別。
我們先從線程棧開始。
1. 圖4-2展示了已加載了CLR的一個Windows進程。在這個進程中,可能存在多個線程。一個線程創建時,會分配到一個1MB大小的棧。這個棧的空間用于向方法傳遞實參,并用于方法內部定義的局部變量。圖4-2展示了一個線程的棧內存(右側)。棧是從高地址向低地址構建的。在圖中,線程已執行了一些代碼,現在,假定線程開始執行的代碼要調用M1方法了。

2. 在一個最基本的方法中,會有一些"序幕"代碼,負責在方法開始時做它工作之前對其進行初始化。另外,還包括了"尾聲"代碼,負責在方法完成工作之后對其進行清理,然后才返回至調用者。M1方法開始執行時,它的"序幕"代碼就會在線程棧上分配局部變量name的內存,如圖4-3所示。

3. 然后,M1調用M2的方法,將局部變量name作為一個實參來傳遞。這造成name局部變量中的地址被壓入棧(參見圖4-4)。在M2方法內部,將使用名為s的參數變量來標識棧位置(有的CPU架構會通過寄存器來傳遞實參,以提高性能)。另外,調用一個方法時,還會將一個"返回地址"壓入棧中。被調用的方法在結束后,應該返回到這個位置(同樣參見圖4-4)。

5.然后,M2方法內部的代碼開始執行。最后,M2抵達它的return語句,造成CPU的指令指針被設置成棧中的返回地址,而且M2的棧幀會展開,使之看起來類似于圖4-3。之后,M1將繼續執行在M2調用之后的代碼,M1的棧幀將準確反映M1需要的狀態?! ?img src="http://s1.VeVb.com/20150728/091620148005959.jpg" alt="" /> 6. 最后,M1會返回到它的調用者。同樣的是通過CPU的指令指針設置成返回地址來實現的(這個返回地址在圖中未顯示,但它應該剛好在棧中的name實參上方),而且M1的棧幀會展開,使之看起來類似于圖4-2。之后,調用了M1的方法會繼續執行在M1之后的代碼,那個方法的棧幀將準確反映它需要的狀態?! ?img src="http://s1.VeVb.com/20150728/211624157904.png" alt="" /> CLR運作關系 1. 假定現在有以下兩個類的定義: internal class Employee { public int32 GetYearsEmployed() { ... } public virtual String GenPRogressReport() { ... } public static Employee Lookup(String name) { ... } }internal sealed class Manager : Employee { public override String GenProgressReport() { ... }} 2. 我們的Windows進程已啟動,CLR已加載到其中,托管堆已初始化,而且已創建一個線程(連同它的1MB的??臻g)。該線程已執行了一些代碼,現在馬上就要調用M3的方法。圖4-6展示了目前的狀況。M3方法包含的代碼演示了CLR是如何工作的。

6. 然后,M3執行它的代碼來構造一個Manager對象。這就會在托管堆中創建Manager類型的一個實例(也就是Manager對象)。如4-9所示。和所有對象一樣,Manager對象也有一個"類型對象指針"和"同步塊索引"。該對象還包含必要的字節來容納Manager類型定義的所有實例數據字段,以及容納由Manager的任何基類(Employee和Object)定義的所有實例字段。任何時候在堆上新建一個對象,CLR都會自動初始化內部"類型對象指針",讓它引用(或指向)與對象對應的類型對象(本例就是Manager類型對象)。此外,CLR會先初始化"同步塊索引",并將對象的所有實例字段設為nll或為零(0),在調用類型的構造器(它本質上是可能修改某些實例字段的一個方法)。new操作符會返回Manager對象的內存地址,該內存地址保存在變量e中(e在線程棧上)?! ?img src="http://s1.VeVb.com/20150728/211845555078.jpg" alt="" /> 7. M3的下一行代碼調用Employee的靜態方法Lookup。調用一個靜態方法時,CLR會定位到與定義靜態方法的類型對應的類型對象。然后,JIT編譯器在類型對象的方法表中查找被調用的方法對應的記錄項,對該方法進行JIT編譯(如果需要的話),再調用JIT編譯后的代碼。就本例,假定Enployee的Lookup方法要查詢數據中的Joe。另外,假定數據庫中指出Joe是為Manager,所以在內部,Lookup方法在堆上構造一個新的Manager對象,用Joe的信息初始化它,然后返回該對象的地址。這個地址保存在局部變量e中。如圖4-10所示。值得注意的是,e不再引用第一個Manager對象。事實上,由于沒有變量引用第一個Manager對象,所以它是將來進行垃圾回收時的主要目標。
8. M3的下一行調用Employee的非虛實例方法GetYearsEmployed。調用一個非虛實例方法時,JIT編譯器會找到與"發出調用的那個變量(e)的類型(Emplyee)"對應的類型對象(Employee類型對象)。在本例中,變量e被定義成為一個Employee。如果Employee類型沒有定義這個方法,JIT編譯器會回溯類層次結構(一直到Object),并在沿途的每個類型中查找該方法。之所以能這樣回溯,是因為每個類型對象都有一個字段引用了它的基類型,但在圖中沒有顯示。然后,JIT編譯器在類型對象的方法表中查找引用了被調用方法的記錄項,對方法進行JIT編譯(如果需要的話),再調用JIT編譯后的調用。在本例中,假定Employee的GetYearsEmployed方法返回5,。這個整數就保存在局部變量year中。如圖4-11所示?! ?img src="http://s1.VeVb.com/20150728/211846198353.jpg" alt="" /> 9. M3的下一行代碼調用Empolyee的虛實例方法GenProgressReport。調用一個虛實例方法時,JIT編譯器要在方法中生成一些額外代碼;方法每次調用時,都會執行這些代碼。這些代碼首先檢查發出調用的變量,然后跟隨地址來到發出調用的對象。在本例中,變量e引用的是代表"Joe"的一個Manager對象。然后,代碼檢查對象內出的"類型對象指針"成員,這個成員指向對象的實際類型。然后,代碼在類型對象的方法表中查找引用了被調用方法的記錄項,對方法進行JIT編譯(如果需要的話),再調用JIT編譯后的代碼。在本例中,由于e引用了一個Manager對象,所以會調用Manager的GenProgressReport實現。如圖4-12所示。
總結: 注意,在Employee和Manager類型對象都包含"類型對象指針"成員。這是由于類型對象本質也是對象。CLR創建類型對象時,必須初始化這些成員。初始化成什么呢?CLR開始在一個進程中運行時,會立即為MSCOrLib.dll中定義的System.Type類型創建一個特殊的類型對象。Employee和Manager類型對象都是該類型的"實例".因此,它們的類型對象指針成員會初始化成對System.Type類型對象的引用。如圖4-13?! ?img src="http://s1.VeVb.com/20150728/211846473979.jpg" alt="" /> 當然,System.Type類型對象本身也是一個對象,內部也有一個"類型對象指針"成員。那么這個指針指向的是什么呢?它指向它本身,因為System.Type類型對象本身就是一新聞熱點
疑難解答