帶着問題讀CLR via C#(二)類型基礎


Q1: Object類型包含哪些方法?

A1: Object類型共包含6個方法,Equals, GetHashCode, ToString, GetType, MemberwiseClone和Finalize.

 

Q2: new一個對象的過程是什么?

A2: 1)計算對象所需字節數,包括該類型及其基類型定義的所有實例字段所需的字節數和類型對象指針、同步塊索引所需字節數,類型指針和同步塊索引是CLR用來管理對象的;2)在托管堆上分配該對象所需內存空間;3)初始化類型對象指針和同步塊索引;4)執行構造函數。大多數編譯器都在構造函數中自動生成一段代碼調用基類構造函數,每個類型的構造函數在執行時都會初始化該類型定義的實例字段。5)返回指向新建對象的一個引用,保存在對象變量中。

可用如下代碼驗證第四步:

View Code
 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             TestThree t = new TestThree();
 6             Console.Read();
 7         }
 8     }
 9 
10     class Test
11     {
12         int i;
13         public int I { get; set; }
14 
15         public Test()
16         {
17             Console.WriteLine("This is Test's constructor");
18         }
19     }
20 
21     class TestTwo : Test
22     {
23         public TestTwo()
24         {
25             Console.WriteLine("This is TestTwo's constructor");
26         }        
27     }
28 
29     class TestThree : TestTwo
30     {
31         public TestThree()
32         {
33             Console.WriteLine("This is TestThree's constructor");
34         }
35     }

執行結果如下:

 

Q3: 父類型和子類型間如何進行轉換?

A3: C#允許將一個對象從它的本身類型轉換為它的父類型,這是安全的,不需要做任何額外操作,但要將一個對象從它的本身類型轉換為它的子類型,則必須要顯式轉換,因為可能會失敗。見代碼:

View Code
 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Person person = new Person();
 6             Man man = new Man();
 7             Person p = man;
 8             Man m = person;
 9         }
10     }
11 
12  class Person
13     { }
14 
15  class Man : Person
16     { }

這段代碼是無法編譯通過的,在Main方法的第四行會報一個這樣的錯誤:

Error 1 Cannot implicitly convert type 'TypeBasic.Person' to 'TypeBasic.Man'. An explicit conversion exists (are you missing a cast?) C:\Users\Allen\Documents\Visual Studio 2012\Projects\TypeBasic\TypeBasic\Program.cs 16 21 TypeBasic

很顯然,一個 “男人” 一定是一個人,故可以直接轉換,但一個 “人” 並不一定是一個 “男人”,所以必須要顯式轉換。可將代碼這樣改寫:

View Code
1 // From
2 Man m = person;
3 
4 // To
5 Man m = (Man)person;

這樣就可以成功通過編譯,但是在運行的時卻拋出了異常,很顯然,Person不能被轉換為Man. 什么情況下Person可以被轉換為Man? 見如下代碼:

View Code
 1         static void Main(string[] args)
 2         {
 3             Man man = new Man();
 4             Test(man);
 5         }
 6 
 7         static void Test(Person p)
 8         {
 9             Man m = (Man)p;
10         }

 

Q4: is和as操作符的作用是什么?

A4: is操作符用來判斷一個對象是否屬於某種類型,返回一個布爾值。改寫下上例的Test方法:

View Code
1 static void Test(Person p)
2 {
3     if (p is Man)
4     {
5         Man m = (Man)p;
6     }
7 }

以上代碼共進行了兩次類型檢測,is操作符首先檢測p是否為Man類型,在if的方法體中進行強制轉換時,CLR會再次檢測p的類型,這對性能有一定影響。

as操作符很好的解決了這個問題,再次改寫Test方法:

View Code
1  static void Test(Person p)
2  {
3      Man m = p as Man;
4      if (m != null)
5      { 
6          //...
7      }
8  }

as操作符在檢測p的類型后會直接對p進行類型轉換,返回一個Man類型的對象,若檢測出p不是Man類型,則會返回null. 整個過程只進行了一次類型檢測。

 

Q5: 什么是命名空間?

A5: 命名空間是對類型的邏輯分組,對於編譯器而言,命名空間的作用是使類型名稱變得更長更具唯一性,但CLR並不知道命名空間,訪問一個類型時,CLR需要知道該類型的全名以及它所在程序集。

 

Q6: 命名空間和程序集之間的關系是什么?

A6: 命名空間和程序集間並沒有什么關聯,同一命名空間的類型可以存在於不同程序集,同一程序集中的類型也可以屬於不同命名空間。

 

Q7: 分析以下代碼執行時CLR發生的動作。

View Code
 1 namespace TestConsole
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Employee e;
 8             Int32 year;
 9             e = new Employee();
10             e = Employee.Lookup("Joe");
11             year = e.GetYearsEmployed();
12             e.GenProgressReport();
13         }
14     }
15 
16     class Employee
17     {
18         // 實例方法
19         public Int32 GetYearsEmployed() 
20         {
21             //...
22         }
23         // 虛方法
24         public virtual string GenProgressReport()
25         { 
26             //...
27         }
28         // 靜態方法
29         public static Employee Lookup(string name)
30         { 
31             //...
32         }
33     }
34 
35     class Manager : Employee
36     {
37         // 對父方法重寫
38         public override string GenProgressReport()
39         {
40             //...
41         }
42     }
43 }

A7:

1)CLR檢查該方法內部引用的所有類型(Employee, Int32, Manager, String),確保定義了這些類型的程序集已成功加載;

2)CLR利用程序集的元數據提取這些類型的相關信息,並創建一些數據結構來表示類型本身,如下圖所示:

3)執行"序幕代碼",在線程棧中為局部變量分配內存,並初始化它們,如下圖所示:

4)構建Manager對象,在托管堆中創建一個Manager類型的實例,CLR會初始化該實例的類型對象指針,讓它引用與實例對應的類型對象,本例中為Manager類型對象;此外CLR會初始化同步塊索引,並將該實例所有實例字段設為null或0,再調用構造函數,new操作符會返回該實例內存地址,該地址保存在e中,如下圖:

5)Lookup是一個靜態方法,調用時CLR會定位定義該靜態方法的類型對應的類型對象,然后JIT編譯器在該類型對象的方法表中查找被調用的方法的記錄項,對方法進行JIT編譯(第一次執行),執行編譯后的代碼。本例中,假定查出的實例是一個i額Manager類型,則在堆中創建一個Manager實例,用查出的信息初始化該實例,並返回它的地址儲存在e中,此時,第一個初始化的Manger對象將沒有指針指向它,它成為垃圾回收對象。見下圖:

6)GetYearsLookup是一個非虛實例方法,在調用時,JIT編譯器會找到發出調用的標量(e)的類型對應的類型對象,本例中為Employee類型對象,因為e被定義為了Employee類型。如果Employee中沒有定義該方法,則會繼續向上一層查找,知道查找到Object類型對象,查找到該方法后,JIT編譯器對其進行編譯(第一次執行),再執行變異后的代碼,將執行結果保存在局部變量中。見下圖:

7)GetProgressReport為定義在Employee中的虛方法,調用一個虛方法,JIT編譯器會在方法中生成一些額外代碼,這些代碼在每次調用方法時都會執行。它首先會檢測發出調用的變量,根據地址查找到發出調用的實例,本例為一個Manager對象,然后檢測對象內部的類型指針,找到該對象的實際類型,從實際類型對象的方法列表中查找調用的方法的記錄項,進行JIT編譯(第一次執行),執行變異后代碼。見下圖:

Q8: 如何理解類型對象?

A8: 類型對象本質上也是對象,它也包含類型對象指針成員,CLR創建這些類型對象時,也會對其進行初始化。CLR開始在一個進程中運行時,會立即為MSCorLib.dll中定義的System.Type對象創建一個特殊的類型對象,Q7中的Emloyee和Manager都是Type類型的“實例”,它們的類型對象指針都會指向Type類型對象,而Type類型對象的類型對象指針則會指向自己。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM