Неявное преобразование с нуль-коалесцирующим оператором
Я обнаружил странное поведение моей программы и после дальнейшего анализа смог обнаружить, что, возможно, что-то не так в моих знаниях C# или где-то еще. Я верю, что это моя ошибка, но я нигде не могу найти ответ...
public class B
{
public static implicit operator B(A values)
{
return null;
}
}
public class A { }
public class Program
{
static void Main(string[] args)
{
A a = new A();
B b = a ?? new B();
//b = null ... is it wrong that I expect b to be B() ?
}
}
Переменная "b" в этом коде оценивается как ноль. Я не понимаю, почему это ноль.
Я погуглил и нашел ответ на этот вопрос - неявное приведение результата оператора Null-Coalescing - с официальной спецификацией.
Но, следуя этой спецификации, я не могу найти причину, по которой "b" имеет значение null:(Возможно, я неправильно ее читаю, и в этом случае я прошу прощения за спам.
Если A существует и не является обнуляемым типом или ссылочным типом, возникает ошибка времени компиляции.
... это не тот случай.
Если b является динамическим выражением, тип результата является динамическим. Во время выполнения a сначала оценивается. Если a не нуль, a преобразуется в динамический, и это становится результатом. В противном случае, b оценивается, и это становится результатом.
... это тоже не тот случай.
В противном случае, если A существует и имеет тип NULL, а неявное преобразование существует из b в A0, тип результата - A0. Во время выполнения a сначала оценивается. Если a не нуль, a разворачивается к типу A0, и это становится результатом. В противном случае b вычисляется и преобразуется в тип A0, и это становится результатом.
... A существует, неявное преобразование из b в A0 не существует.
В противном случае, если A существует и существует неявное преобразование из b в A, тип результата - A. Во время выполнения a сначала оценивается. Если a не нуль, a становится результатом. В противном случае b вычисляется и преобразуется в тип A, и это становится результатом.
... A существует, неявное преобразование из b в A не существует.
В противном случае, если b имеет тип B и существует неявное преобразование из a в B, тип результата - B. Во время выполнения a сначала оценивается. Если a не является нулем, a разворачивается в тип A0 (если A существует и допускает обнуление) и преобразуется в тип B, и это становится результатом. В противном случае, b оценивается и становится результатом.
... b имеет тип B, и существует неявное преобразование из a в B. a оценивается как ноль. Следовательно, b должно быть оценено, а b должно быть результатом.
В противном случае, a и b несовместимы, и возникает ошибка времени компиляции. Не бывает
Я что-то упустил, пожалуйста?
4 ответа
Ну, спецификация говорит (я изменяю на x
а также y
для меньшего замешательства здесь):
• В противном случае, если y имеет тип Y и существует неявное преобразование из x в Y, тип результата будет Y. Во время выполнения сначала выполняется оценка x. Если x не нуль, x разворачивается в тип X0 (если X существует и допускает обнуление) и преобразуется в тип Y, и это становится результатом. В противном случае у вычисляется и становится результатом.
Бывает. Во-первых, левая сторона x
что просто a
, проверено на null
, Но это не так null
само по себе. Затем следует использовать левую сторону. Затем выполняется неявное преобразование. Его результат типа B
является... null
,
Обратите внимание, что это отличается от:
A a = new A();
B b = (B)a ?? new B();
В этом случае левый операнд является выражением (x
) который null
сам по себе, и результат становится правой стороной (y
).
Может быть, должны возвращаться неявные преобразования между ссылочными типами null
(если и) только если оригинал null
-А как хорошая практика?
Я думаю, ребята, которые написали спецификацию, могли бы сделать это так (но не сделали):
• В противном случае, если y имеет тип Y и существует неявное преобразование из x в Y, тип результата будет Y. Во время выполнения x сначала оценивается и преобразуется в тип Y. Если выходные данные этого преобразования не равны NULL, этот результат становится результатом. В противном случае у вычисляется и становится результатом.
Может быть, это было бы более интуитивно понятно? Это заставило бы среду выполнения вызвать ваше неявное преобразование, независимо от того, были ли входные данные для преобразования null
или нет. Это не должно быть слишком дорого, если типичные реализации быстро определили, что null → null
,
Почему вы ожидали, что оператор с нулевым слиянием вернется new B()
? a
не нуль, так a ?? new B()
оценивает a
,
Теперь, когда мы знаем, что a
будет возвращено, нам нужно определить тип результата (T
) и нужно ли нам снимать a
в T
,
• В противном случае, если b имеет тип B и существует неявное преобразование из a в B, тип результата - B. Во время выполнения сначала оценивается a. Если a не является нулем, a разворачивается в тип A0 (если A существует и допускает обнуление) и преобразуется в тип B, и это становится результатом. В противном случае, b оценивается и становится результатом.
Неявное преобразование существует из A
в B
, так B
тип результата выражения. Что значит a
будет неявно приведен к B
, И ваш неявный оператор возвращает null
,
На самом деле, если вы пишете var b = a ?? new B();
(обратите внимание на var
), вы увидите, что компилятор выводит B
быть типом, возвращаемым выражением.
В противном случае, если b имеет тип B и существует неявное преобразование из a в B, тип результата - B. Во время выполнения a сначала оценивается. Если a не является нулем, a разворачивается в тип A0 (если A существует и допускает обнуление) и преобразуется в тип B, и это становится результатом. В противном случае, b оценивается и становится результатом.
... b имеет тип B, и существует неявное преобразование из a в B. a оценивается как ноль. Следовательно, b должно быть оценено, а b должно быть результатом.
Вы интерпретируете это неправильно. Ничто так не говорит a
в B
преобразование сделано раньше null
проверка выполнена. Это заявляет, что null
проверка сделана до конвертации!
Ваш случай подходит к этому:
Если a не является нулем, a разворачивается в тип A0 (если A существует и допускает обнуление) и преобразуется в тип B, и это становится результатом.
Часть, на которую мы должны обратить внимание, это тип времени компиляции выражения с нулевым слиянием.
В противном случае, если b имеет тип B и существует неявное преобразование из a в B, тип результата - B. Во время выполнения a сначала оценивается. Если a не является нулем, a разворачивается в тип A0 (если A существует и допускает обнуление) и преобразуется в тип B, и это становится результатом. В противном случае, b оценивается и становится результатом.
Чтобы поместить это в псевдокод:
public Tuple<Type, object> NullCoalesce<TA, TB>(TA a, TB b)
{
...
else if (a is TB) // pseudocode alert, this won't work in actual C#
{
Type type = typeof(TB);
object result;
if (a != null)
{
result = (TB)a; // in your example, this resolves to null
}
else
{
result = b;
}
return new Tuple<Type, object>(type, result);
}
...
}