從來就不可能精通:關於Boxing


我的簡歷上從來就不敢出現“精通”兩個字,但是每次有招聘簡歷推薦過來的時候,各種“精通”就映入了我的眼簾。無奈啊,確實還是不行啊。例如今天在逛園子的時候發現這篇文章的回復中出現了關於boxing的討論。發現有一個情況我原來的理解是錯誤的。於是留此一篇備忘。

一般來說當一個 value type 實例需要轉換為一個 reference type 實例的時候需要進行裝箱。例如:

int number = 2;
object o = number;
o.ToString();

如果查看其IL,可以發現 box 指令。在一般情況下,例如將value type實例 cast為一個reference type(或者接口形式)實例,或者函數參數的類型轉換(轉換為 reference type,或者cast 為 接口)。但是只有 box 指令才說明發生了裝箱的操作嗎?實際上還有其他的情況。

首先,value type和其他的類型一樣,都可以擁有自己的靜態的或者實例的成員,包括方法(包括虛方法)和field。我們當然可以調用其虛方法,但是當我們必須通過虛函數表去查找虛函數的時候則必須boxing然后再去callvirt,如果僅僅是作為成員函數調用value type實例的虛函數實現的話則不需要boxing,直接用call指令就OK了。例如:

int number = 3;
number.ToString();

其 IL 代碼是:

IL_0001:  ldc.i4.3    
IL_0002:  stloc.0     
IL_0003:  ldloca.s    00 
IL_0005:  call        System.Int32.ToString

這里直接調用 call 方法觸發實例方法。並不用裝箱。

但是如果這樣,令value type實例調用其沒有實現的虛方法:

var valueInstance = new ValueClass();
valueInstance.ToString();

就會產生如下的 IL 代碼:

IL_0001:  ldloca.s    00 
IL_0003:  initobj     ValueClass
IL_0009:  ldloca.s    00 
IL_000B:  constrained. ValueClass
IL_0011:  callvirt    System.Object.ToString

其中沒有 box 指令, 但是有 constrained. 指令。constrained. 指令和 callvirt 使用稱為 constrained virtual call,這是在 CLR 2.0引入的。主要目的是為了處理泛型類型的實例化或者方法調用,不管泛型類型實際參數是value type 還是 reference type。但是constrained. 指令后面跟的類型參數並不一定非得是泛型參數,可以直接是具體類型。這種調用的規則如下:

  • 如果是一個reference type(此時這個實例的 this 指針是一個managed pointer,指向該reference type 實例),則this指針復引用返回的是reference type實例的引用。虛函數的調用就發生在reference type實例上。
  • 如果是一個value type(此時這個實例的this指針是一個managed pointer,指向該 value type實例),並且該value type實現了該函數,那么接下來的調用實際上是一個非虛調用,並直接作用在該 value type 實例上(這是因為 value type 都是 seal 的,其實現了的虛方法不可能再被其他的派生類使用)。
  • 如果是一個value type,並且該類型並沒有實現基類的虛方法(其基類肯定是System.Object,System.ValueType或者System.Enum),則this指針的復引用會返回value type實例,並直接被boxing為object reference。virtcall會作用在object reference上。

我們可以看到,最后一條就是我們前一段范例代碼中的情況。

當然,調用基類的非虛方法也需要boxing,例如:

var valueInstance = new ValueClass();
valueInstance.GetType();

其首先會裝箱, 然后直接用 call 調用基類的非虛方法。

IL_0001:  ldloca.s    00 
IL_0003:  initobj     ValueClass
IL_0009:  ldloc.0     
IL_000A:  box         ValueClass
IL_000F:  call        System.Object.GetType

總結一下,在以下的情況下可能發生裝箱:

  1. 值類型和引用類型(接口)的類型轉換,參數傳遞;
  2. 調用值類型實例未實現的基類的虛方法;
  3. 調用值類型父類的非虛方法。

還有其它情況嗎?歡迎補充。


免責聲明!

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



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