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

來源:互聯網
上載者:User

標籤:strong   尋找   cee   函數   完成   引用   方法   public   dynamic   

 

對於方法的調用,很是令我頭疼,什麼靜態方法,執行個體方法,執行個體虛方法,這裡查了很多資料,總結如下:

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

思路:

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 Name6751cd88 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 Name6751cd88 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 Name6751cd88 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#類的方法表的建立和方法的調用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.