Примечание: похоже, это было исправлено в Roslyn
Этот вопрос возник при написании моего ответа на этот , который говорит об ассоциативности нуль-сливающегося оператора .
Напомним, что идея оператора слияния нулей состоит в том, что выражение вида
x ?? y
сначала оценивает x
, потом:
- Если значение
x
равно нулю,y
оценивается, и это является конечным результатом выражения - Если значение
x
не равно нуль,y
это не оцениваются, а значениеx
является конечным результатом выражения, после преобразования к типу компиляции времени ,y
если это необходимо
Теперь обычно нет необходимости в преобразовании, или это просто из обнуляемого типа в необнуляемый тип - обычно это одинаковые типы или просто от (скажем) int?
до int
. Однако вы можете создавать свои собственные операторы неявного преобразования, и они используются там, где это необходимо.
Для простого случая x ?? y
я не видел никакого странного поведения. Тем не менее, с (x ?? y) ?? z
некоторыми я вижу смутное поведение.
Вот короткая, но полная тестовая программа - результаты в комментариях:
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;
}
}
Таким образом, у нас есть три пользовательских типа значений A
, B
и C
, с преобразованиями из A в B, A в C и B в C.
Я могу понять и второй случай, и третий случай ... но почему в первом случае происходит дополнительное преобразование A в B? В частности, я действительно ожидал, что первый и второй регистры будут одинаковыми - в конце концов, это всего лишь извлечение выражения в локальную переменную.
Есть ли кто-нибудь о том, что происходит? Я крайне неохотно плачу «ошибку», когда дело доходит до компилятора C #, но я в тупике о том, что происходит ...
РЕДАКТИРОВАТЬ: Хорошо, вот более неприятный пример того, что происходит, благодаря ответу конфигуратора, который дает мне еще одну причину думать, что это ошибка. РЕДАКТИРОВАТЬ: образец даже не нуждается в двух нуль-объединяющих операторов сейчас ...
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;
}
}
Выход этого:
Foo() called
Foo() called
A to int
Тот факт, что Foo()
здесь дважды вызывается, удивляет меня - я не вижу причин для того, чтобы выражение было оценено дважды.
C? first = ((B?)(((B?)x) ?? ((B?)y))) ?? ((C?)z);
. Вы получите:Internal Compiler Error: likely culprit is 'CODEGEN'
(("working value" ?? "user default") ?? "system default")