2016-04-08 12 views
7

Considerare: Come gestisce C# chiamando un metodo di interfaccia su una struttura?

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

E la IL per principale:

.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 

First (IL_000a), S::M() si chiama con un tipo di valore per this. Successivamente (IL_0011), viene chiamato con un tipo di riferimento (in scatola).

Come funziona?

Mi vengono in mente tre modi:

  1. due versioni I::M sono compilati, per il valore di tipo/ref. Nel vtable, memorizza quello per il tipo di ref, ma le chiamate inviate staticamente usano quella per i tipi di valore. Questo è brutto e improbabile, ma possibile.
  2. In vtable, memorizza un metodo "wrapper" che cancella lo this, quindi chiama il metodo effettivo. Sembra inefficiente perché tutti gli argomenti del metodo dovrebbero essere copiati attraverso due chiamate.
  3. C'è una logica speciale che verifica questo in callvirt. Ancora più inefficiente: tutti gli callvirt s incorrono in una (leggera) penalità.
+0

Nota 'IL_0002: casella S'. La struttura è in scatola per consentire la chiamata. Anche se c'è un modo per aggirare questo con i generici. – Joey

+0

@Joey che è per 'I i = s;'; la prima chiamata usa il tipo di valore 'ldloca.s s' – valtron

+1

È contorto e guardare l'IL non aiuta affatto. È richiesta una comprensione di base del modo in cui vengono utilizzati gli invii di invio nel CLR. Vance Morrison del team CLR lo spiega abbastanza bene in [questo post del blog] (https://blogs.msdn.microsoft.com/vancem/2006/03/13/scavo-into-interfaccia-chiamate-in-a base-net-quadro-stub-spedizione /). –

risposta

2

La risposta breve è che nel metodo stesso, il valore della struct è sempre accessibile tramite un puntatore. Ciò significa che il metodo non funziona come se il struct fosse passato come parametro normale, è più simile a un parametro ref. Significa anche che il metodo non sa se sta funzionando su un valore in box o meno.

La risposta lunga:

In primo luogo, se compilo il codice, quindi s.M(); non genera alcun codice. Il compilatore JIT è abbastanza intelligente per inline il metodo e inline un metodo vuoto non produce codice. Quindi, quello che ho fatto è applicare [MethodImpl(MethodImplOptions.NoInlining)] su S.M per evitare questo.

Ora, ecco il codice nativo il metodo genera (funzione omettendo di prologo ed epilogo):

// 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] 

In entrambi i casi, il call finisce per chiamare lo stesso codice (che è solo un singolo ret istruzioni) . La prima volta, il registro CX punta allo stack assegnato s (SP + 28h nel codice precedente), la seconda volta allo stack assegnato i (AX + 8 subito dopo la chiamata alla funzione di allocazione dell'heap).

+0

Quindi 'questo' è sempre un puntatore; in un metodo di tipo ref, punta al campo di informazioni sul tipo (immediatamente prima del primo campo) e in un tipo di valore punta direttamente al primo campo, che può essere un puntatore interno a un valore box_. – valtron

Problemi correlati