.NET(C#):IL中的虛函數調用和constrained指令

來源:互聯網
上載者:User

關於參考型別,虛方法的調用比較直接簡單,直接看這個類:

class a

{

    public virtual void doo()

    { }

}

class b : a

{

    public override void doo()

    {

        base.doo();

    }

}

 

非常簡單,b繼承a,改寫a的doo方法,並且又調用基類a的doo方法。看b的doo方法的IL:

.method public hidebysig virtual instance void doo() cil managed

{

    .maxstack 8

    L_0000: nop

    L_0001: ldarg.0

    L_0002: call instance void Mgen.a::doo()

    L_0007: nop

    L_0008: ret

}

可以看到,在類方法中調用基類的虛方法(或者其他方法),直接用call指令。後面的instance代表非靜態方法。

 

接著在主函數中這樣做:

new b().doo();

((a)new b()).doo();

 

即通過b和a分別調用一個b對象的doo方法:

L_0000: nop

L_0001: newobj instance void Mgen.b::.ctor()

L_0006: callvirt instance void Mgen.a::doo()

L_000b: nop

L_000c: newobj instance void Mgen.b::.ctor()

L_0011: callvirt instance void Mgen.a::doo()

L_0016: nop

不難,呵呵。上面兩句指令完全是等價的,所以IL都一樣。由於這個虛函數是定義在類a中的,所以子類調用永遠等效於使用callvirt的a::doo()函數。

參考型別的虛函數調用在IL上是比較直接的,在實值型別的調用上會稍有些複雜,下面來看實值型別的。

 

 

實值型別不能進行繼承操作,所有實值型別隱式繼承System.ValueType,所以實值型別不能定義虛函數,我們看這個結構體:

struct MyStruct

{

    public override string ToString()

    { return null; }

 

    /* 沒有改寫GetHashCode方法 */

}

我們改寫了Object.ToString()虛函數,但是沒有改寫GetHashCode虛函數。

 

接下來,主函數代碼:

MyStruct ms = new MyStruct();

ms.ToString();

ms.GetHashCode();

ms.GetType();

分別調用了3個函數:

  • ToString:改寫基類的虛函數。
  • GetHashCode:未改寫的基類虛函數。
  • GetType:基類非虛函數。

IL:

//初始化:ms = new MyStruct();

L_0000: nop

L_0001: ldloca.s ms

L_0003: initobj Mgen.MyStruct

 

//ToString

L_0009: ldloca.s ms

L_000b: constrained. Mgen.MyStruct

L_0011: callvirt instance string [mscorlib]System.Object::ToString()

L_0016: pop

   

//GetHashCode

L_0017: ldloca.s ms

L_0019: constrained. Mgen.MyStruct

L_001f: callvirt instance int32 [mscorlib]System.Object::GetHashCode()

L_0024: pop

   

//GetType

L_0025: ldloc.0

L_0026: box Mgen.MyStruct  //裝箱

L_002b: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

L_0030: pop

先來看非虛函數GetType,這個函數是在Object類中定義的,而實值型別在棧中的,不能像堆中的參考型別那樣隨心所欲得調用繼承來的成員,必須先通過裝箱才可以的。所以調用一個實值型別的GetType,實際上CLR會在堆中建立一個裝箱的對象,然後調用這個對象的GetType。

 

事實上,調用未改寫的虛函數也是一樣的,所以上例中調用GetHashCode應該需要裝箱的,但是GetHashCode和ToString的IL是一樣的(當然除了函數簽名),都是用的callvirt,然後對應函數是System.Object類的成員。原因就是上面那個constrained指令。這個constrained專門用在callvirt之前,來看MSDN的解釋:

 

 

http://msdn.microsoft.com/zh-cn/library/system.reflection.emit.opcodes.constrained.aspx

當 callvirt method 指令前面帶有首碼 constrained thisType,該指令將按照以下步驟執行:

  1. 如果 thisType 為參考型別(相對於實值型別),則 ptr 被取值 (Dereference),並作為“this”指標傳遞到 method的 callvirt。

  2. 如果 thisType 為實值型別,而且 thisType 實現 method,則 ptr 作為“this”指標在不作任何修改的狀態下傳遞到 call 。 method 指令,以便 thisType 實現 method。

  3. 如果 thisType 為實值型別,而且 thisType 不實現 method,則將取消對 ptr 的引用,對它進行裝箱,然後將它作為“this”指標傳遞到 callvirt 指令。 method指令。

 

 

原來constrained指令會根據類型確保後面的callvirt會調用成功,所以上面的GetHashCode函數可以直接用callvirt而不用box IL指令,因為有了constrained指令,CLR會自動在執行callvirt時對實值型別變數進行裝箱。而上面的ToString方法,加不加constrained都無所謂,因為實值型別已經重寫了Object的ToString方法,callvirt總會成功的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.