Nota: isso parece ter sido corrigido em Roslyn
Essa pergunta surgiu ao escrever minha resposta a esta , que fala sobre a associatividade do operador de coalescência nula .
Apenas como lembrete, a ideia do operador de coalescência nula é que uma expressão da forma
x ?? y
primeiro avalia x
e depois:
- Se o valor de
x
for nulo,y
for avaliado e esse for o resultado final da expressão - Se o valor de
x
for não nulo, nãoy
for avaliado e o valor de for o resultado final da expressão, após uma conversão para o tipo de tempo de compilação, se necessáriox
y
Agora, normalmente, não há necessidade de conversão, ou é apenas de um tipo anulável para um não anulável - geralmente os tipos são os mesmos ou apenas de (digamos) int?
para int
. No entanto, você pode criar seus próprios operadores implícitos de conversão, e esses são usados sempre que necessário.
Para o simples caso de x ?? y
, eu não vi nenhum comportamento estranho. No entanto, com (x ?? y) ?? z
eu vejo algum comportamento confuso.
Aqui está um programa de teste curto, mas completo - os resultados estão nos comentários:
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;
}
}
Portanto, temos três tipos de valores personalizados A
, B
e C
, com conversões de A para B, A para C e B para C.
Eu posso entender o segundo e o terceiro casos ... mas por que há uma conversão extra de A para B no primeiro caso? Em particular, eu realmente esperava que o primeiro e o segundo casos fossem a mesma coisa - afinal, é apenas extrair uma expressão em uma variável local.
Algum comprador sobre o que está acontecendo? Estou extremamente hesitante em chorar "bug" quando se trata do compilador C #, mas estou perplexo com o que está acontecendo ...
EDIT: Ok, aqui está um exemplo mais desagradável do que está acontecendo, graças à resposta do configurador, o que me dá mais motivos para pensar que é um bug. EDIT: A amostra nem precisa de dois operadores coalescentes nulos agora ...
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;
}
}
A saída disso é:
Foo() called
Foo() called
A to int
O fato de Foo()
ser chamado duas vezes aqui é extremamente surpreendente para mim - não vejo motivo para a expressão ser avaliada duas vezes.
C? first = ((B?)(((B?)x) ?? ((B?)y))) ?? ((C?)z);
. Você obterá:Internal Compiler Error: likely culprit is 'CODEGEN'
(("working value" ?? "user default") ?? "system default")