有没有办法内联元组解构以避免不必要的分配?

Is there a way to inline tuple deconstruction to avoid an unnecessary allocation?

我有下面的例子struct:

struct Data {
    internal long a;
    internal long b;

    internal void Deconstruct(out long aa, out long bb) {
        aa = a; bb = b;
    }
}

如果我只想使用结构的值而忘记结构本身怎么办?

Data Generate()
    => new Data() { a = 3, b = 5 };

void Test() {
    (var a, var b) = Generate();
    Console.WriteLine(a);
    Console.WriteLine(b);
}

Generate 的调用创建了一个结构,并立即将其分解为各个部分。我能以某种方式内联这个过程并完全摆脱结构吗?

我在发布模式下使用 VS 15.5.7 编译(这个 class 库),一个 ilspy 显示:

.method private hidebysig 
    instance void Test () cil managed 
{
    // Method begins at RVA 0x208c
    // Code size 33 (0x21)
    .maxstack 3
    .locals init (
        [0] int64,
        [1] valuetype WhosebugDemo.Q1/Data,
        [2] int64,
        [3] int64
    )

    IL_0000: ldarg.0
    IL_0001: call instance valuetype WhosebugDemo.Q1/Data WhosebugDemo.Q1::Generate()
    IL_0006: stloc.1
    IL_0007: ldloca.s 1
    IL_0009: ldloca.s 2
    IL_000b: ldloca.s 3
    IL_000d: call instance void WhosebugDemo.Q1/Data::Deconstruct(int64&, int64&)
    IL_0012: ldloc.2
    IL_0013: ldloc.3
    IL_0014: stloc.0
    IL_0015: call void [System.Console]System.Console::WriteLine(int64)
    IL_001a: ldloc.0
    IL_001b: call void [System.Console]System.Console::WriteLine(int64)
    IL_0020: ret
} // end of method Q1::Test

如果你想忘记结构,而不是

Data Generate()
    => new Data() { a = 3, b = 5 };

为什么不用

void Generate(out long aa, out long bb)
{
    aa = 3; bb = 5;
}

这消除了你所要求的结构。

如果你想保留结构但去掉额外的分配,你也可以这样做:

class Data {
    public long A { get; internal set; }
    public long B { get; internal set; }

    internal Data(long a, long b) {
        A = a; B = b;
    }
}

然后使用:

void Test() {
    var data = Generate();
    Console.WriteLine(data.A);
    Console.WriteLine(data.B);
}

根据 mikez 的推荐,我使用 windbgsos 扩展来查看 jitted 代码。

我从中学到了三件事:

  • jitting后,在这种情况下堆栈上没有分配结构
  • 查看生成的 il 代码不足以推断运行时执行的代码
  • 这个简化的示例可能与我原来的用例不同 - 所以我必须查看我的原始示例的 jitted 代码

感谢您提供的所有有用信息。

在不进一步修改原始源代码的情况下,这是 Test 方法的 !Name2EE 的输出:

C:\Users\...\source\repos\WhosebugDemo\Q1.cs @ 37:
>>> 00007ffa`02c41d00 56              push    rsi
00007ffa`02c41d01 4883ec30        sub     rsp,30h
00007ffa`02c41d05 33c0            xor     eax,eax
00007ffa`02c41d07 4889442420      mov     qword ptr [rsp+20h],rax
00007ffa`02c41d0c 4889442428      mov     qword ptr [rsp+28h],rax
00007ffa`02c41d11 488d542420      lea     rdx,[rsp+20h]
00007ffa`02c41d16 e8dde6ffff      call    00007ffa`02c403f8 (WhosebugDemo.Q1.Generate(), mdToken: 0000000006000003)
00007ffa`02c41d1b 488b4c2420      mov     rcx,qword ptr [rsp+20h]
00007ffa`02c41d20 488b742428      mov     rsi,qword ptr [rsp+28h]

C:\Users\...\source\repos\WhosebugDemo\Q1.cs @ 38:
00007ffa`02c41d25 e8eee5ffff      call    00007ffa`02c40318 (System.Console.WriteLine(Int64), mdToken: 0000000006000080)

C:\Users\...\source\repos\WhosebugDemo\Q1.cs @ 39:
00007ffa`02c41d2a 488bce          mov     rcx,rsi
00007ffa`02c41d2d e8e6e5ffff      call    00007ffa`02c40318 (System.Console.WriteLine(Int64), mdToken: 0000000006000080)

C:\Users\...\source\repos\WhosebugDemo\Q1.cs @ 40:
00007ffa`02c41d32 90              nop
00007ffa`02c41d33 4883c430        add     rsp,30h
00007ffa`02c41d37 5e              pop     rsi
00007ffa`02c41d38 c3              ret

对于 Generate 方法:

C:\Users\...\source\repos\WhosebugDemo\Q1.cs @ 28:
>>> 00007ffa`02c41d50 b803000000      mov     eax,3
00007ffa`02c41d55 b905000000      mov     ecx,5
00007ffa`02c41d5a 488902          mov     qword ptr [rdx],rax
00007ffa`02c41d5d 48894a08        mov     qword ptr [rdx+8],rcx
00007ffa`02c41d61 488bc2          mov     rax,rdx
00007ffa`02c41d64 c3              ret

据我了解,堆栈上没有分配结构。