Nota: esto parece haberse solucionado en Roslyn
Esta pregunta surgió cuando escribí mi respuesta a esta , que habla sobre la asociatividad del operador de fusión nula .
Solo como recordatorio, la idea del operador de fusión nula es que una expresión de la forma
x ?? y
primero evalúa x
, luego:
- Si el valor de
x
es nulo,y
se evalúa y ese es el resultado final de la expresión - Si el valor de
x
es no nulo,y
se no evaluado, y el valor dex
es el resultado final de la expresión, después de una conversión con el tipo de tiempo de compilación dey
si es necesario
Ahora, por lo general, no hay necesidad de una conversión, o es solo de un tipo anulable a uno no anulable; por lo general, los tipos son los mismos, o simplemente de (digamos) int?
a int
. Sin embargo, puede crear sus propios operadores de conversión implícitos, y estos se usan cuando es necesario.
Por el simple caso de x ?? y
, no he visto ningún comportamiento extraño. Sin embargo, con (x ?? y) ?? z
veo un comportamiento confuso.
Aquí hay un programa de prueba corto pero completo: los resultados están en los comentarios:
using System;
public struct A
{
public static implicit operator B(A input)
{
Console.WriteLine("A to B");
return new B();
}
public static implicit operator C(A input)
{
Console.WriteLine("A to C");
return new C();
}
}
public struct B
{
public static implicit operator C(B input)
{
Console.WriteLine("B to C");
return new C();
}
}
public struct C {}
class Test
{
static void Main()
{
A? x = new A();
B? y = new B();
C? z = new C();
C zNotNull = new C();
Console.WriteLine("First case");
// This prints
// A to B
// A to B
// B to C
C? first = (x ?? y) ?? z;
Console.WriteLine("Second case");
// This prints
// A to B
// B to C
var tmp = x ?? y;
C? second = tmp ?? z;
Console.WriteLine("Third case");
// This prints
// A to B
// B to C
C? third = (x ?? y) ?? zNotNull;
}
}
Entonces, tenemos tres tipos de valores personalizados A
, B
y C
, con conversiones de A a B, A a C y B a C.
Puedo entender tanto el segundo caso como el tercer caso ... pero ¿por qué hay una conversión adicional de A a B en el primer caso? En particular, realmente hubiera esperado que el primer caso y el segundo caso fueran lo mismo: después de todo, es solo extraer una expresión en una variable local.
¿Algún tomador de lo que está pasando? Soy extremadamente reticente a gritar "error" cuando se trata del compilador de C #, pero estoy perplejo en cuanto a lo que está sucediendo ...
EDITAR: De acuerdo, aquí hay un ejemplo más desagradable de lo que está sucediendo, gracias a la respuesta del configurador, lo que me da más razones para pensar que es un error. EDITAR: La muestra ni siquiera necesita dos operadores de fusión nula ahora ...
using System;
public struct A
{
public static implicit operator int(A input)
{
Console.WriteLine("A to int");
return 10;
}
}
class Test
{
static A? Foo()
{
Console.WriteLine("Foo() called");
return new A();
}
static void Main()
{
int? y = 10;
int? result = Foo() ?? y;
}
}
El resultado de esto es:
Foo() called
Foo() called
A to int
El hecho de que me Foo()
llamen dos veces aquí me sorprende enormemente: no veo ninguna razón para que la expresión se evalúe dos veces.
C? first = ((B?)(((B?)x) ?? ((B?)y))) ?? ((C?)z);
. Obtendrá:Internal Compiler Error: likely culprit is 'CODEGEN'
(("working value" ?? "user default") ?? "system default")