c# - C# 如何处理调用结构上的接口(interface)方法?

标签 c# .net struct interface cil

考虑:

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

以及 Main 的 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),使用引用(盒装)类型调用它。

这是如何运作的?

我可以想到三种方法:

  1. I::M 的两个版本被编译,用于 value/ref 类型。在 vtable 中,它存储一个用于 ref 类型的,但静态调度的调用使用一个用于值类型的。 这很丑陋,不太可能,但有可能。
  2. 在 vtable 中,它存储了一个“包装器”方法,该方法将 this 拆箱,然后调用实际方法。 这听起来效率很低,因为所有方法的参数都必须通过两次调用来复制。
  3. callvirt 中有特殊的逻辑检查这一点。 效率更低:所有 callvirt 都会受到(轻微)惩罚。

最佳答案

简短的回答是,在方法本身中,struct 的值始终通过指针 访问。这意味着该方法不会像 struct 作为普通参数传递一样运行,它更像是一个 ref 参数。这也意味着该方法不知道它是否正在对装箱值进行操作。

长答案:

首先,如果我编译你的代码,那么 s.M(); 不会生成任何代码。 JIT 编译器足够智能,可以内联方法,内联一个空方法不会产生任何代码。所以,我所做的是在 S.M 上应用 [MethodImpl(MethodImplOptions.NoInlining)] 来避免这种情况。

现在,这是您的方法生成的 native 代码(省略函数序言和结尾):

// 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 最终都会调用相同的代码(这只是一条 ret 指令)。第一次CX寄存器指向栈分配的s(上面代码中的SP+28h),第二次指向堆分配的i(AX+ 8 在调用堆分配函数之后)。

关于c# - C# 如何处理调用结构上的接口(interface)方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36491956/

相关文章:

c# - swig + 单声道 : C# example errors of not finding the library

c#如何循环遍历HttpRequestHeaders的所有 header

c# - 找到最好的原型(prototype)设计工具

.net - T4MVC 异步 Controller

c++ - 对结构数组(卡片)进行排序

c - 多次包含的头文件中的 Typedef

c++ - 动态访问结构体内部的变量 C++

c# - ListView 上的工具提示

c# - 我需要一个正则表达式来验证斯里兰卡车号

c# - C# 中高数字的 System.DivideByZeroException