La igualdad entre los tipos de tupla de valor se introdujo en C# 7.3. Permite código como este:
var x = (1, 2); var y = (1, 2); if(x == y) ...Esto funciona bien y da el resultado correcto. Sin embargo, el compilador introduce copias ocultas de los objetos de tupla y los compara en su lugar, dando el equivalente a esto:
var V_3 = x; var V_4 = y; if(V_3.Item1 == V_4.Item1 && V_3.Item2 == V_4.Item2) ... ¿Son V_3 y V_4 solo copias defensivas? Si es así, ¿de qué se están defendiendo?
Este es el más pequeño, por ejemplo, que se me ocurrió, pero lo he intentado con estructuras definidas por el usuario y devoluciones/propiedades de métodos como miembros de tupla también con resultados similares (no siempre idénticos).
Estoy usando v5.0.202 de .Net SDK y C# v9, pero he visto este comportamiento desde .Net Core 3.
La respuesta es que MSIL, el lenguaje intermedio que está descompilando en C#, es un lenguaje basado en pilas, y los lenguajes basados en pilas tienen dificultades para hacer referencia a lo que no está en la parte superior de la pila. Entonces, en su lugar, puede agregar una variable local y almacenar resultados que no puede alcanzar fácilmente.
Y la razón por la que esta aparente ineficiencia no se optimiza es que simplemente no importa. Cuando se aplica JIT al código nativo, el compilador emite comparaciones directas y un salto como cabría esperar:
var rng = new Random(); var v1=(rng.Next(), rng.Next()); var v2=(rng.Next(), rng.Next()); return v1 == v2;genera (saltando la inicialización y restaurando la pila en el prólogo):
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