我的簡歷上從來就不敢出現“精通”兩個字,但是每次有招聘簡歷推薦過來的時候,各種“精通”就映入了我的眼簾。無奈啊,確實還是不行啊。例如今天在逛園子的時候發現這篇文章的回復中出現了關於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
總結一下,在以下的情況下可能發生裝箱:
- 值類型和引用類型(接口)的類型轉換,參數傳遞;
- 調用值類型實例未實現的基類的虛方法;
- 調用值類型父類的非虛方法。
還有其它情況嗎?歡迎補充。