为什么在将 C# 值元组与 == 进行比较时,编译器会创建隐藏副本?

Why are there hidden copies created by the compiler when comparing C# Value Tuples with ==?

C# 7.3 中引入了值元组类型之间的相等性。它允许这样的代码:

var x = (1, 2);
var y = (1, 2);

if(x == y) ...

这工作正常并给出了正确的结果。但是,编译器引入了元组对象的隐藏副本,并改为比较它们,给出等效于此:

var V_3 = x;
var V_4 = y;
if(V_3.Item1 == V_4.Item1 && V_3.Item2 == V_4.Item2) ...

V_3V_4只是防御副本吗?如果是,他们在防御什么?

这是我能想出的最小示例,但我已经尝试使用用户定义的结构和方法 returns/properties 作为元组成员以及类似(不总是相同)的输出。

我使用的是 .Net SDK 的 v5.0.202 和 C# v9,但自 .Net Core 3 以来就出现过这种行为。

答案是 MSIL,即您要反编译回 C# 的中间语言,是一种基于堆栈的语言,而基于堆栈的语言很难引用不在堆栈顶部的内容。因此,您可以添加一个局部变量并存储您无法轻松访问的结果。

这种看似低效的问题之所以没有得到优化,是因为它根本不重要。当 JIT 为本机代码时,编译器会像您期望的那样发出直接比较和跳转:

    var rng = new Random();
    var v1=(rng.Next(), rng.Next());
    var v2=(rng.Next(), rng.Next());
    
    return v1 == v2;

生成(跳过初始化并恢复序言中的堆栈):

L004f: cmp edi, ebp
L0051: jne short L0064
L0053: cmp ebx, eax
L0055: sete al                // == && ==?
L0058: movzx eax, al          // return true && 2nd equality result
L0063: ret
L0064: xor eax, eax           // return false
L006e: ret