程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]4. 類型基礎及類型、對象、棧和堆運行時的相互聯系

[CLR via C#]4. 類型基礎及類型、對象、棧和堆運行時的相互聯系

編輯:C#入門知識






  由於所有類型最終都是從System.Object派生的,所以可以保證每個類型的每個對象都有一組最基本的方法。

  System.Object提供了如下所示的公共實例方法。  

Equals(Object) 確定指定的對象是否等於當前對象。如果兩個對象具有相同值就返回ture. GetHashCode 返回對象的值得一個哈希碼。如果某個類型的對象要在哈希表集合中作為key使用,該類型應該重寫這個方法。方法應該為不同的對象提供一個良好的分布。 ToString 該方法默認返回類型的完整名稱(this.GetType().FullName)。 GetType 返回從Type派生的一個對象的實例,指出調用GetType的那個對象是什麼類型。返回的Type類型可以與反射類配合使用,從而獲取與對象的類型相關的元數據信息。

  

  

MemberwiseClone 這個非虛方法能創建類型的一個新實例,並將對象的實例字段設為與this對象的實例字段完全一致。返回的是對新實例的一個引用 Finalize 在垃圾回收器判斷對象應該被作為垃圾收集之後,在對象的內存被實際回收之前,會調用這個虛方法。需要在回收之前執行一些清理工作的類型應該重寫這個方法。

  

Employee e =  Employee();

  以下是new操作符所做的事情:

  1)它計算類型及其所有基類型(一直到System.Object)中定義的所有實例需要的字節數。堆上的每個對象都需要一些額外的開銷成員——"類型對象指針(type object pointer)"和"同步塊索引"(sync block index)。這些成員由CLR用於管理對象。這些額外成員的字節數會計入對象大小。

  2)它從托管堆中分配指定類型要求的字節數,從而分配對象的內存,分配的所有字節都設為零(0)。

  3)它初始化對象的"類型對象指針"和"同步塊索引"成員。

  4)調用類型的實例構造器,向其傳入對new的調用中指定的任何實參(本例中是"

  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)。

  

  4. M2的方法開始執行時,它的"序幕"代碼就是在線程棧中為局部變量length和tally分配內存。如圖4-5所示。然後,M2方法內部的代碼開始執行。最後,M2抵達它的return語句,造成CPU的指令指針被設置成棧中的返回地址,而且M2的棧幀會展開, 使之看起來類似於圖4-3。之後,M1將繼續執行在M2調用之後的代碼,M1的棧幀將准確反映M1需要的狀態。       5. 最後,M1會返回到它的調用者。同樣的是通過CPU的指令指針設置成返回地址來實現的(這個返回地址在圖中未顯示,但它應該剛好在棧中的name實參上方),而且M1的棧幀會展開,使之看起來類似於圖4-2。之後,調用了M1的方法會繼續執行在M1之後的代碼,那個方法的棧幀將准確反映它需要的狀態。      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的棧空間)。該線程已執行了一些代碼,現在馬上就要調用M3的方法。圖4-6展示了目前的狀況。M3方法包含的代碼演示了CLR是如何工作的。

   

  3. 當JIT編譯器將M3的IL代碼轉換成為本地CPU指令時,會注意到M3的內部引用的所有類型:Employee、Int32、Manager以及String(因為"Joe")。這個時候,CLR要確保定義了這些類型的所有程序集都已經加載。然後,利用這些程序集的元數據,CLR提取與這些類型有關的信息,並創建一些數據結構表示類型本身。圖4-7展示了為Employee和Manager類型對象使用的數據結構。由於這個線程在調用M3之前已經執行了一些代碼,所有不妨假定Int32和String類型對象已經創建好了,所以圖中沒有顯示它們。       4. 先前提過,堆上的所有對象上都包含兩個額外的成員:"類型對象指針"和"同步塊索引"。如圖4-7所示,Employee和Manager類型對象都有這兩個成員。定義一個類型時,可以在類型的內部定義靜態數據字段。為這些靜態字段數據提供支持的字節是在類型對象自身中分配到的。在每個類型對象中,都包含一個方法表。在方法表中,類型中定義的每個方法都有一個對應的記錄項。由於Employee有3個方法就有3個記錄項,Manager只有一個方法,也就只有一個記錄項。          5. 現在,當CLR確定方法需要的所有類型對象都已經創建了,而且M3的代碼也已經編譯好了,就允許線程開始執行M3的本地代碼。M3的"序幕"代碼執行時,必須在線程棧中為局部變量分配內存,如4-8所示。作為方法的"序幕"代碼的一部分,CLR會自定將所有局部變量初始化為null或零(0)。       6. 然後,M3執行它的代碼來構造一個Manager對象。這就會在托管堆中創建Manager類型的一個實例(也就是Manager對象)。如4-9所示。和所有對象一樣,Manager對象也有一個"類型對象指針"和"同步塊索引"。該對象還包含必要的字節來容納Manager類型定義的所有實例數據字段,以及容納由Manager的任何基類(Employee和Object)定義的所有實例字段。任何時候在堆上新建一個對象,CLR都會自動初始化內部"類型對象指針",讓它引用(或指向)與對象對應的類型對象(本例就是Manager類型對象)。此外,CLR會先初始化"同步塊索引",並將對象的所有實例字段設為nll或為零(0),在調用類型的構造器(它本質上是可能修改某些實例字段的一個方法)。new操作符會返回Manager對象的內存地址,該內存地址保存在變量e中(e在線程棧上)。       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所示。      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。      當然,System.Type類型對象本身也是一個對象,內部也有一個"類型對象指針"成員。那麼這個指針指向的是什麼呢?它指向它本身,因為System.Type類型對象本身就是一個類型對象的"實例"。   現在,我們總算理解了CLR的整個類型系統及其工作方式。System.Object的GetType方法返回的是存儲在指定對象的"類型對象指針"成員中的地址。也就是說,GetType方法返回的是指向對象的類型對象的一個指針。這樣一來,就可以判斷系統中任何對象(包括類型對象本身)的真實類型。           

GetType

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved