2016-04-08 6 views
7

は考えてみましょう: C#はどのようにして構造体のインタフェースメソッドを呼び出すのですか?

interface I { void M(); } 
struct S: I { public void M() {} } 
// in Main: 
S s; 
I i = s; 
s.M(); 
i.M(); 

を、メイン用IL:

.maxstack 1 
.entrypoint 
.locals init (
    [0] valuetype S s, 
    [1] class I i 
) 

IL_0000: nop 
IL_0001: ldloc.0 
IL_0002: box S 
IL_0007: stloc.1 
IL_0008: ldloca.s s 
IL_000a: call instance void S::M() 
IL_000f: nop 
IL_0010: ldloc.1 
IL_0011: callvirt instance void I::M() 
IL_0016: nop 
IL_0017: ret 

ファースト(IL_000a)、S::M()thisのための値の型と呼ばれています。次の(IL_0011)、参照(ボックス)タイプで呼び出されます。

これはどのように機能しますか?

私は、以下の3つの方法を考えることができます:I::M

  1. 2つのバージョンが値/ refのタイプのために、コンパイルされます。 vtableには、ref型のものが格納されますが、静的にディスパッチされた呼び出しは、値型用のものを使用します。 これは醜い可能性は低いですが、可能です。
  2. vtableには、thisをアンボックスした「ラッパー」メソッドが格納され、実際のメソッドが呼び出されます。 これは、すべてのメソッドの引数が2回の呼び出しでコピーされなければならないため、非効率的です。
  3. callvirtにこれをチェックする特別なロジックがあります。 さらに効率が悪い:すべてcallvirtは、(わずかな)ペナルティを負う。
+0

注:IL_0002:ボックスSです。構造体は、呼び出しを許可するために囲まれています。ジェネリック医薬品を使ってその周りに道がありますが。 – Joey

+0

@Joeyこれは '私は= sの;';最初の呼び出しは値型 'ldloca.s s'を使用します。 – valtron

+1

これは複雑であり、ILを見ても全く助けにはなりません。 CLRのディスパッチスタブの使用方法の基本的な理解が必要です。 CLRチームのVance Morrisonは[このブログの記事](https://blogs.msdn.microsoft。com/vancem/2006/03/13/net-framework-stub-based-dispatch /でインタフェースコールを呼び出す –

答えて

2

短い答えは、方法自体は、structの値は常にポインタを通してアクセスされることです。これは、通常のパラメータとしてstructが渡されたかのようにメソッドが動作しないことを意味します。これは、refパラメータによく似ています。また、メソッドがボックス化された値で動作しているかどうかを知ることもできません。

長い答え:

まず、私はあなたのコードをコンパイルする場合、s.M();は、任意のコードを生成しません。 JITコンパイラはメソッドをインライン化するのに十分スマートで、空のメソッドをインライン化するとコードが生成されません。だから、これを避けるためにをS.Mに適用することです。

は今、ここにあなたの方法は、(関数プロローグとエピローグを省略)を生成ネイティブコードは次のとおりです。いずれの場合も

// initialize s in register AX 
xor   eax,eax 
// move s from register AX to stack (SP+28h) 
mov   qword ptr [rsp+28h],rax 
// load pointer to MethodTable for S to register CX 
mov   rcx,7FFDB00C5B08h 
// allocate memory for i on heap 
call  JIT_TrialAllocSFastMP_InlineGetThread (07FFE0F824C10h) 
// copy contents of s from stack to register C 
movsx  rcx,byte ptr [rsp+28h] 
// copy from register CX to heap 
mov   byte ptr [rax+8],cl 
// copy pointer to i from register AX to register SI 
mov   rsi,rax 
// load address to c on stack to register CX 
lea   rcx,[rsp+28h] 
// call S::M 
call  00007FFDB01D00C8 
// copy pointer to i from register SI to register CX 
mov   rcx,rsi 
// move address of stub for I::M to register 11 
mov   r11,7FFDB00D0020h 
// ??? 
cmp   dword ptr [rcx],ecx 
// call stub for I::M 
call  qword ptr [r11] 

callは、(1つだけretの命令である)同じコードを呼び出して終了します。最初に、CXレジスタはスタック割り当て0x(上記のコードではSP + 28h)を指し、2回目はヒープ割り当てのi(ヒープ割り当て関数の呼び出し直後のAX + 8)を指します。

+0

したがって、 'this'は常にポインタです。 ref型メソッドでは、型情報フィールド(最初のフィールドの直前)をポイントし、値型では、最初のフィールドを直接指します。これは、囲まれたvalue_の内部ポインタになります。 – valtron

関連する問題