CLR要求所有型別最終都要從System.Object派生。也就是所,下面的兩個定義是完全相同的,
//隱式派生自System.Object class Employee { ..... }
//顯示派生子 System.Object class 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
}
名稱空間和程式集不一定是相關的,也就是說它們之間沒有必然聯絡。
----------------------------------------------------------------------------------
現在將解釋型別、物件、執行緒棧和託管堆在執行時的相互聯絡。此外,還將解釋呼叫靜態方法、例項方法和虛方法的區別。
我們先從執行緒棧開始。
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)。
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的棧空間)。該執行緒已執行了一些程式碼,現在馬上就要呼叫M3的方法。圖4-6展示了目前的狀況。M3方法包含的程式碼演示了CLR是如何工作的。
GetType