加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

C#如何处理在结构上调用接口方法?

发布时间:2020-12-15 07:45:21 所属栏目:百科 来源:网络整理
导读:考虑: interface I { void M(); }struct S: I { public void M() {} }// in Main:S s;I i = s;s.M();i.M(); 以及IL for Main: .maxstack 1.entrypoint.locals init ( [0] valuetype S s,[1] class I i)IL_0000: nopIL_0001: ldloc.0IL_0002: box SIL_0007:
考虑:
interface I { void M(); }
struct S: I { public void M() {} }
// in Main:
S s;
I i = s;
s.M();
i.M();

以及IL for Main:

.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()用它的值类型调用.接下来(IL_0011),使用引用(盒装)类型调用它.

这是如何运作的?

我可以想到三种方式:

>编译两个版本的I :: M,用于value / ref类型.在vtable中,它存储一个用于ref类型,但静态调度的调用使用一个用于值类型.这是丑陋的,不太可能,但可能.
>在vtable中,它存储一个“包装”方法,将其解包,然后调用实际方法.这听起来效率低下,因为所有方法的参数都必须通过两次调用来复制.
>有一些特殊的逻辑可以在callvirt中检查这一点.效率更低:所有的callvirts都会受到轻微的惩罚.

解决方法

简短的回答是,在方法本身中,结构的值总是通过指针访问.这意味着该方法不像结构作为普通参数传递一样运行,它更像是一个ref参数.这也意味着该方法不知道它是否在盒装值上运行.

答案很长:

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

现在,这是您的方法生成的本机代码(省略函数prolog和epilog):

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

在这两种情况下,调用最终都会调用相同的代码(这只是一个ret指令).第一次,CX寄存器指向堆栈分配的s(上面代码中的SP 28h),第二次指向堆分配的i(刚刚调用堆分配函数后的AX 8).

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读