Hello World
Spiga

如何在不装箱的前提下调用“显式”实现的接口方法?(答案)

2013-04-07 13:51 by 老赵, 5361 visits

之前的问题是:假如一个struct实现了某个接口,却“显式”实现了其中的成员,那么我们又该如何访问这些成员?其实已经有不少同学抓住了关键,那就是使用泛型,例如有人提出了这样的辅助方法:

static void Dispose<T>(T obj) where T : IDisposable {
    obj.Dispose();
}

我们没有进行类型转化,只是让运行时可以“认识到”类型T实现了IDisposable接口,这自然可以在不装箱的情况下调用其成员。可惜的是,这种做法的“意识”到位了,却是错误的,原因在于忽视了值类型传参的特点:复制所有内容。换句话说,这个辅助方法内部所使用的obj其实是一个副本,而不是原来的参数对象。假如被调用的成员会修改自身状态——尽管这对于值类型来说是一种极差的设计——这便会产生问题。所以正确的方法应该是这样的:

static void Dispose<T>(ref T obj) where T : IDisposable {
    obj.Dispose();
}

有了ref关键字修饰obj参数,在传递参数的时候,我们传递的是这个参数的“位置”,因此最终Dispose方法是调用在传入的参数对象上的。此外,我们还可以用.NET 4.0中的AggressiveInlining标注该方法,这样它会被确保内联,一是减少调用开销,二是避免运行时为每个不同的struct类型各自生成一份代码。这个方法的IL代码如下:

.method private hidebysig static 
    void Dispose<([mscorlib]System.IDisposable) T> (
        !!T& obj
    ) cil managed flag(0100) 
{
    // Method begins at RVA 0x266b
    // Code size 13 (0xd)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: constrained. !!T
    IL_0007: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    IL_000c: ret
} // end of method Program::Dispose

根据我们之前的分析,这里的代码意味着不会产生装箱。当然,这还是“不那么能说明情况”,所以还是来看看汇编代码更为直接。首先,准备这么一些简单代码:

struct DisposableStruct : IDisposable {
    private readonly int _i, _j;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public DisposableStruct(int i, int j) {
        _i = i;
        _j = j;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    public void Dispose() {
        Console.WriteLine(_i + _j);
    }
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DisposeRef<T>(ref T obj) where T : IDisposable {
    obj.Dispose();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DisposeNoRef<T>(T obj) where T : IDisposable {
    obj.Dispose();
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void Test() {                        // Line 40
    var ds = new DisposableStruct(1, 2);    // Line 41
    ds.Dispose();                           // Line 42
}                                           // Line 43

[MethodImpl(MethodImplOptions.NoInlining)]
static void TestNoRef() {                   // Line 46
    var ds = new DisposableStruct(1, 2);    // Line 47
    DisposeNoRef(ds);                       // Line 48
}                                           // Line 49

[MethodImpl(MethodImplOptions.NoInlining)]
static void TestRef() {                     // Line 52
    var ds = new DisposableStruct(1, 2);    // Line 53
    DisposeRef(ref ds);                     // Line 54
}                                           // Line 55

注意这里我们对于AggressiveInliningNoInlining的精心标注,包括构造函数,因为这样才能清晰地展现出问题来。先看Test方法的代码(所有地址省去高位的000007fd,下同):

Normal JIT generated code
Program.Test()
Begin 03670120, size 32

...\Program.cs @ 41:
03670120    sub     rsp,38h
03670124    mov     qword ptr [rsp+20h],0
0367012d    mov     r8d,2
03670133    mov     edx,1
03670138    lea     rcx,[rsp+20h]
0367013d    call    0355c098 (DisposableStruct..ctor(Int32, Int32), ...)

...\Program.cs @ 42:
03670142    lea     rcx,[rsp+20h]
03670147    call    0355c0a8 (DisposableStruct.Dispose(), ...)
0367014c    nop
0367014d    add     rsp,38h
03670151    ret

对比TestNoRef方法:

Normal JIT generated code
Program.TestNoRef()
Begin 03670220, size 45

...\Program.cs @ 47:
03670220    sub     rsp,38h
03670224    mov     qword ptr [rsp+28h],0
0367022d    mov     qword ptr [rsp+20h],0
03670236    mov     r8d,2
0367023c    mov     edx,1
03670241    lea     rcx,[rsp+28h]
03670246    call    03670170 (DisposableStruct..ctor(Int32, Int32), ...)

...\Program.cs @ 48:
0367024b    mov     r11,qword ptr [rsp+28h]
03670250    mov     qword ptr [rsp+20h],r11
03670255    lea     rcx,[rsp+20h]
0367025a    call    03670190 (DisposableStruct.Dispose(), ...)
0367025f    nop
03670260    add     rsp,38h
03670264    ret

可以清晰的看到,DisposableStruct对象分配在[rsp+28h]上,但在调用Dispose方法前复制了一份到[rsp+20h]DisposableStruct恰好8字节),把它作为Dispose方法的参数。前两个方法很容易明白,但TestRef则显得“奇怪”了一些:

Normal JIT generated code
Program.TestRef()
Begin 036701c0, size 48

...\Program.cs @ 53:
036701c0    push    rbx
036701c1    sub     rsp,30h
036701c5    mov     qword ptr [rsp+28h],0
036701ce    lea     rbx,[rsp+28h]
036701d3    mov     qword ptr [rsp+20h],0
036701dc    mov     r8d,2
036701e2    mov     edx,1
036701e7    lea     rcx,[rsp+20h]
036701ec    call    03670170 (DisposableStruct..ctor(Int32, Int32), ...)

...\Program.cs @ 54
036701f1    mov     rax,qword ptr [rsp+20h]
036701f6    mov     qword ptr [rbx],rax
036701f9    mov     rcx,rbx
036701fc    call    03670190 (DisposableStruct.Dispose(), ...)
03670201    nop
03670202    add     rsp,30h
03670206    pop     rbx
03670207    ret

其实我挺没有想到TestRef的方法需要这么多指令,最理想的情况其实应该和Test方法一模一样,不是么?这里虽然没有装箱,也没有复制对象,但是做的事情太多了,居然还在栈上保留rbx,要知道这可是64位机。当然,我想这也是因为我们强制禁止了JIT内联DisposableStruct构造函数的缘故吧,感兴趣的朋友可以再自行尝试一下。

毕竟,我们现在只是想确认这里不会产生装箱。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

8 条回复

  1. 小红
    114.249.33.*
    链接

    小红 2013-06-27 22:42:27

    那这篇文章的结果是?

  2. 链接

    陈虎 2014-02-14 17:43:27

    很是佩服,希望能得到你的一些指点

  3. lililala6868
    35.215.136.*
    链接

    lililala6868 2021-02-19 19:59:51

    福彩3D这个辅助方法内部所使用的obj其实是一个副本,而不是原来的参数对象。假如被调用的成员会修改自身状态——尽管这对于值类型来说是一种极差的设计——这便会产生体彩排列5问题

  4. zoroseo2020
    35.215.141.*
    链接

    zoroseo2020 2022-11-30 10:39:59

    也许你现在仍然是一个人下班,一个人乘地铁,一个人上楼,一个人吃饭,一个人睡觉,一个人发呆。很多人离开另外一个人,就没有了自己。而你却一个人,度过了所有。 幸运飞艇走势图 福彩双色球走势图 幸运时时彩走势图

  5. kericnnoe
    111.242.194.*
    链接

    kericnnoe 2022-12-21 04:06:56

    你選擇了歐博娛樂城,就代表著你已經成功一半了

  6. Air Jordan
    147.185.242.*
    链接

    Air Jordan 2025-02-12 13:47:28

  7. Jordan 1
    216.126.233.*
    链接

    Jordan 1 2025-03-27 00:23:45

  8. Yeezy Slides
    104.234.236.*
    链接

    Yeezy Slides 2025-04-26 20:39:56

    Love it or hate it, the Yeezy Foam Runner has become a cultural phenomenon. From its polarizing design to its sky-high resale value, this shoe is more than just footwear—it’s a statement. Whether you’re a hypebeast, a sustainability advocate, or just someone who loves comfy kicks, here’s why the Foam Runner deserves your attention (and maybe a spot in your closet).

    1. The Design: “Ugly” Never Looked This Good When Kanye West first dropped the Foam Runner in 2020, critics called it the “weirdest Yeezy ever” 4. But fast-forward to today, and its chunky, alien-esque silhouette has become a streetwear staple. The shoe’s one-piece EVA foam construction features bold cutouts for breathability and a futuristic aesthetic that screams avant-garde 79.

    Why it works:

    Versatility: Pair it with socks for a cozy fall vibe or go barefoot for summer beach days.

    Bold Aesthetics: The organic curves and “biomorphic” design make it stand out in a sea of basic sneakers 4.

    Unisex Appeal: Available in adult, kid, and infant sizes, it’s a family flex 79.

    1. Sustainability Meets Hype Kanye promised eco-friendly innovation, and the Foam Runner delivers. Made with algae-based foam and recycled materials, each pair helps clean lakes by repurposing harmful algae 79. Even the packaging is minimalist to reduce waste.

    But let’s keep it real: While the eco-credentials are impressive, limited drops and insane resale prices (up to 10x retail!) have left fans frustrated 34. Original retail was 75 – 75–80, but scarcity pushed some colorways to $6,000+ in resale markets 34.

    1. Colorways That Keep Us Obsessed The Foam Runner’s ever-expanding palette is a masterclass in hype. Recent drops include:

    “Ochre” & “MX Cream Clay”: Earthy tones perfect for minimalist outfits (August 2 drop, $80) 210.

    “Mineral Blue”: A moody midnight hue for stealthy flexing 5.

    “Onyx”: Sleek all-black for the “dark mode” enthusiasts (released June 2022) 7.

    “MIST”: A misty brown-gray blend that sold out in seconds 9.

    Styling hacks:

    Socks + Sandals 2.0: Throw on colorful crew socks for a retro vibe.

    Techwear Essential: Match it with cargo pants and a puffer vest.

    Beach-Ready: Water-resistant material means you can rock it poolside 7.

    1. The Hype Train Isn’t Slowing Down From Walmart’s $24 knockoffs (shut down after legal battles 1) to Chinese brands like Peak copying the design 3, the Foam Runner’s influence is undeniable. But nothing beats the OG.

    Why it’s worth the hunt:

    Cultural Impact: It redefined “ugly chic” and inspired a wave of (hole-punched shoes) 6.

    Collector’s Item: Early colorways like the “Marbled” edition are grails for sneakerheads 4.

    Final Word: How to Cop Want to avoid resale scams? Stay alert for restocks! New colorways like “MXT Moon Grey” (2023) and future drops are your best shot at scoring retail 6. Set those app notifications, pray to the Yeezus, and remember: in the world of Foam Runners, patience pays.

    TL;DR: The Yeezy Foam Runner is weird, wonderful, and the shoe of the decade. Grab a pair, wear it with confidence, and join the cult.

    Ready to flex? Drop your Foam Runner stories in the comments!

    Yeezy Slides
    Yeezy Official Website
    Jordan 1
    Jordan 12
    Jordan 11
    Yeezy Slides
    NFL Shop
    NFL Store
    Yeezy Slides
    Yeezy 350
    Jordan 4
    Yeezy Supply
    Jordan 4
    Yeezy Website
    Yeezy Slides
    Jordan 12
    Jordan 11
    Jordan 1
    Air Jodan
    NFL Jerseys
    Yeezy Slides
    Yeezy Supply
    Yeezy Store
    Yeezy Slides
    Louis Vuitton Outlet
    Louis Vuitton Outlet
    Yeezy Shoes
    Yeezy Slides
    Yeezy 350
    Yeezy Slides
    Yeezy Adidas
    Yeezy Official Website
    Adidas Yeezy
    Yeezy Supply
    Yeezy Slides
    Yeezy Official
    Yeezy 350
    Yeezy Slides

    Tags:Yeezy Shoes,Yeezy Slides,Jordan 1,Air Jordan,Jordan

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我