1. 程式人生 > >c#類的方法表的建立和方法的調用

c#類的方法表的建立和方法的調用

方法覆蓋 cep 過程 final 提高 用法比較 stat 構造過程 調用方法

對於方法的調用,很是令我頭疼,什麽靜態方法,實例方法,實例虛方法,這裏查了很多資料,總結如下:

這裏聲明,我也是菜鳥,這裏只討論方法的調用相關的技術,屬於個人理解,如有錯誤,請指正

思路:

1 clr在加載類型的過程中方法表是怎麽樣構建的?

2 在程序調用方法時是怎樣確定使用哪個類型的方法表的?

3 在程序調用方法時是怎樣確定方法在方法表中的位置的(位於方法表的第幾個方法)?

一 、方法在方法表中的排列順序:

繼承的實例虛方法、實例虛方法、構造函數、靜態方法、實例方法

技術分享

方法表排列原則:

1 在類的方法表的構造過程中:虛方法總是在子類的方法表中被復制的;實例方法,構造函數,靜態方法等其他方法則在子類的方法表中不繼承的

2 在類的方法表中:虛方法總是排在方法表的開頭位置;繼承的虛方法在最前面,新建的虛方法緊隨其後(如圖)

3 虛方法後邊依次排列的是構造函數、靜態方法、實例方法

為什麽把“繼承的實例虛方法”和“實例虛方法”放在方法表的開頭位置?

在這種情況下每個虛方法在 相關的類的 方法表中 的位置都是不變的(無論是在其在創建方法的類中還是在派生類中):比如一個虛方法在類中的次序是第k個,那麽他在其子類或父類(如果父類中有這個方法)中的位置都是第k個。

如果子類中新添加了虛方法,因為在新填的虛方法之前,已經把父類的方法表中的虛方法都復制到了子類的方法表最前面,所以父類中所有的方法在其子類中的位置序號都是不變的。

如果子類中新添加了除了虛方法之外的其他方法(實例方法,構造函數,靜態方法等),這些方法也都是排在虛方法之後

以上兩點就保證了虛方法無論是在其自身的類、父類、子類中其在方法表中的位置(位於方法表的第幾個)都是不變的

結論:方法表中虛方法的排序,可以在類的層次結構中保持虛方法的層次結構,這是實現多態的基礎,也就是為什麽說繼承是實現多態的基礎了。

例子:

類的定義代碼如下:

技術分享
    class Program
    {
        static void Main(string[] args)
        {
            Father son = new Son();
            son.DoWork();
            son.DoVirtualWork();
            son.DoVirtualAll();

            Son.DoStaticWork();

            Father aGrandson 
= new Grandson(); aGrandson.DoWork(); aGrandson.DoVirtualWork(); aGrandson.DoVirtualAll(); Console.ReadKey(); } } public class Father { public void DoWork() { Console.WriteLine("Father.DoWork()"); } public virtual void DoVirtualWork() { Console.WriteLine("Father.DoVirtualWork()"); } public virtual void DoVirtualAll() { Console.WriteLine("Father.DoVirtualAll()"); } } public class Son : Father { public static void DoStaticWork() { Console.WriteLine("Son.DoStaticWork()"); } public new void DoWork() { Console.WriteLine("Son.DoWork()"); } public new virtual void DoVirtualWork() { Console.WriteLine("Son.DoVirtualWork()"); } public override void DoVirtualAll() { Console.WriteLine("Son.DoVirtualAll()"); } } public class Grandson : Son { public override void DoVirtualWork() { Console.WriteLine("Grandson.DoVirtualWork()"); } public override void DoVirtualAll() { Console.WriteLine("Grandson.DoVirtualAll()"); } } public class GrandGrandson : Grandson { public new virtual void DoVirtualWork() { Console.WriteLine("GGson.DovirtualWork()"); } public override void DoVirtualAll() { Console.WriteLine("GGson.DoVirtualAll()"); } }
示例代碼 技術分享
 Entry       MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
001dc035 001d382c   NONE MethodInvoke.Father.DoVirtualAll()
00320158 001d3834    JIT MethodInvoke.Father..ctor()
00320190 001d3818    JIT MethodInvoke.Father.DoWork()
Father類的方法表 技術分享
   Entry    MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()////前四個方法是繼承自Object類的方法
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
00320200 001d38b8    JIT MethodInvoke.Son.DoVirtualAll()//這也是繼承的Father類的虛方法,只不過在Son類中重寫的方法覆蓋了//這兩個類是繼承自Father類的方法
001dc059 001d38b0   NONE MethodInvoke.Son.DoVirtualWork()//這個是Son類中新建的方法
00320120 001d38c0    JIT MethodInvoke.Son..ctor()//Son類的構造函數
00320238 001d3898    JIT MethodInvoke.Son.DoStaticWork()//Son類的靜態方法
001dc055 001d38a4   NONE MethodInvoke.Son.DoWork()//Son類的實例方法
Son類的方法表 技術分享
   Entry     MethodDe    JIT Name
6751cd88 672360bc PreJIT System.Object.ToString()
67516a90 672360c4 PreJIT System.Object.Equals(System.Object)
67516660 672360e4 PreJIT System.Object.GetHashCode()
675967c0 672360f8 PreJIT System.Object.Finalize()
003201c8 001d3824    JIT MethodInvoke.Father.DoVirtualWork()
003202a8 001d3930    JIT MethodInvoke.Grandson.DoVirtualAll()
001dc079 001d3928   NONE MethodInvoke.Grandson.DoVirtualWork()
00320270 001d3938    JIT MethodInvoke.Grandson..ctor()
Grandson類的方法表

二、方法表中方法的確定:

1 最簡單的是非虛的方法

    這個只有一種情況,方法在哪個類中,該方法就是在那個類中定義的。因為這些非虛的方法,並不會在其子類中復制其方法。Son類的方法表中最後兩個方法

MethodInvoke.Son.DoStaticWork()
MethodInvoke.Son.DoWork()這兩個類是Son類中定義的,因此也只有Son類的方法表中才會有這兩個方法
就驗證了這個說法。

2 對於虛方法

方法表中的方法有可能有三種來源

a 來自其所在類新建的虛方法,這種情況會在方法表中適當的位置新加一個方法表槽(使用new virtual 和virtual關鍵字)

例如:public new virtual void DoVirtualWork()和public virtual void NewMethod()

b 通過繼承父類並且在類中重新定義的虛方法,這種情況在把父類的方法復制到該類的方法表中後,使用重新定義的方法將其覆蓋掉,不會新建方法表槽(使用override關鍵字)

 例如:public override void DoVirtualWork()

c 通過繼承父類的虛方法,這種情況不用使用任何關鍵字,他只是把父類的方法復制到該類的方法表中。

b c兩種其實都繼承了父類中該方法在方法表中的位置,而a則是在該類的方法表中新添加了位置(新見了方法表槽)

三、方法的調用:

要講明白方法的調用,先要解釋幾個名詞(自己理解的名詞)

引用變量:是指在聲明時的那個變量,如object a;這裏的a就是引用變量

對象、實例:在實例化中建立的那個對象,如new object(),會創建一個object對象(並且返回一個對象的引用)

從C#到IL:

首先看看從C#語言到IL語言,C#編譯器是怎麽翻譯的

在調用方法的時候,C#編譯器關註的是引用變量的類型,它並不會關心實例類型是什麽。C#編譯器會從引用變量的類型開始向其父類逐層查找:

a 對於實例虛方法,直到查找到virtual關鍵字的時候,就會翻譯為該方法的調用。如:

  對於上面代碼中的類型,如果我有代碼Father gd=new Grandson();gd.DoVirtualWork(),那麽在IL中會翻譯成callvirt/call Father::DoVirtualWork()

  如果有代碼Grandson gd=new Grandson();gd.DoVirtualWork(),那麽在IL中就會翻譯成callvirt/call Son::DoVirtualWork()

這裏通常情況用的是callvirt,但在有些情況是會用call的:

-比如一個密封類引用的虛方法就可以用call,因為可以確定沒有派生類,不會調用派生類中的方法了,使用call可以避免進行類型檢查,提高性能

-值類型調用虛方法時也會用call,值類型首先是一個密封類型,其次call調用可以阻止值類型被執行裝箱

-在類型定義中,調用基類的虛方法時,采用call可以避免callvirt遞歸調用本身引起的堆棧溢出,如

技術分享
class call_callvirt
{
      public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }      
}    
調用基類虛方法

b 對於非虛方法,直到查找到第一個含該方法的定義類時,就會調用該方法。如:

  對於上面代碼中的類型,如果我有代碼Father gd=new Grandson();gd.DoWork(),那麽在IL中會翻譯成call/callvirt Father::DoWork()

  如果有代碼Grandson gd=new Grandson();gd.DoWork(),那麽在IL中就會翻譯成call/callvirt Son::DoVirtualWork()

  這裏通常用的用的是call,但也有使用callvirt的情況:

-常見的在引用類型中使用callvirt,因為引用變量為null時會拋出異常NullReferenceException,而call則不會拋出任何異常,為類型安全起見在C#中會調用callvirt來完成非虛類型的調用

總的來說,在C#中對方法的調用在IL中基本都翻譯成了call/callvirt(還有calli在C#中我很少見倒)指令調用方法,雖然call和callvirt用法比較亂,但是骨子裏還是有區別的:

call用來調用靜態類型、聲明類型的方法,而callvirt調用動態類型、實際(實例)類型的方法

從IL到localcode

這裏首先要講的就是IL中call和callvirt的區別了:

call 直接調用函數(由上一部分知道,這裏調用的函數是由引用變量的類型決定的);

執行靜態調度:在編譯期間就可以確定其執行的操作(編譯的時候就可以確定使用哪個類型的方法表

callvirt會檢查引用變量所指向的實例的類型(包括是否是null引用),並且在實例的類型的方法表中調用對應位置的方法(這個命令實際上就是說知道了方法在方法表中的位置,由實例的類型決定使用哪個方法表中對應位置的方法)

執行動態調度:在運行時才能確定執行的操作(需要運行時判斷引用變量所指向的實例的類型,進而確定該實例類型的方法表為要使用的方法表

另外的方法調用:

基於反射技術的動態調度機制,基本原理是在運行時,查找方法表的信息來實施調用的方式。常見的方式有:MethodInfo.Invoke()方式和動態方法委托(Dynamic Method Delegate)方式。

四、貼出去的代碼的結果如下圖:

技術分享

回答問題:

假定有ABC三個類型,且A<--B<--C

1 clr在加載類型的過程中方法表是怎麽樣構建的?

  這裏只關心方法表,類的其他部分忽略:clr在實例化類型的實例的時候,需要在之前加載好類型:

加載object類(如果還未加載,下同);

然後加載類A,在這個過程中先將object類的虛方法復制在A類的方法表中,然後依次排列A類的虛方法,構造函數,靜態方法,實例方法;

然後加載類B,在這個過程中先將A類的虛方法復制在A類的方法表中,然後依次排列B類的虛方法,構造函數,靜態方法,實例方法;

然後加載類C,在這個過程中先將B類的虛方法復制在C類的方法表中,然後依次排列C類的虛方法,構造函數,靜態方法,實例方法;

.....依次類推,這就構成了各個類自己的方法表,方法表中包括了繼承的虛方法、虛方法、構造函數、靜態方法、實例方法

2 在程序調用方法時是怎樣確定使用哪個類型的方法表的?

對於非虛方法:“引用變量”是哪個類型,就使用哪個類型的方法表

對於虛方法:“引用變量指向的對象類型” 是什麽類型,就使用哪個類型的方法表

3 在程序調用方法時是怎樣確定方法在方法表中的位置的(位於方法表的第幾個方法)?

即“引用變量類型”的方法表中該方法的位置(由於虛方法表的特點決定了 虛方法的位置 在 類層次結構中的各個類的方法表 中 是相同的,也即虛方法的位置被其子類繼承了下來)

後記:這篇文章查了網上很多牛人的博客,還有參考了《你必須知道的.net》王濤 等資料,感謝這些牛人的辛勤勞動成果,自己寫了才知道來之不易啊。由於筆者首次寫,有很多不足之處,希望指正

c#類的方法表的建立和方法的調用