Ответ 1
Несколько вещей.
Во-первых, условным оператором является тернарный оператор, а не третичный оператор.
Во-вторых, я отмечаю, что в ваших образцах кода два примера кода, которые должны быть эквивалентными, не являются:
short foo;
if (isValid)
foo = -1;
else
getFoo();
не совпадает с
short foo = isValid ? (short)-1 : getFoo();
Бывший оставляет foo неназначенным, если isValid - false. Последний присваивает foo независимо от значения isValid.
Я предполагаю, что вы имели в виду
short foo;
if (isValid)
foo = -1;
else
foo = getFoo();
и что, кроме того, getFoo() возвращает короткий.
Вопрос заключается в том, почему преобразование в условном операторе без типа cast является незаконным, но в результате утверждения if является законным.
Это законно в выражении if, поскольку в разделе 6.1.9 спецификации указано:
Константное выражение типа int может быть преобразовано в тип sbyte, byte, short, ushort, uint или ulong, если значение константного выражения находится в пределах диапазона целевого типа.
-1 - это постоянное выражение типа int, которое находится в диапазоне коротких значений, поэтому оно может быть преобразовано в короткий неявно.
Итак, почему условное выражение формирует фикцию?
Первое, что нам необходимо установить, - это правило, согласно которому тип условного выражения определяется из его содержимого, а не из его контекста. Тип выражения в правой части присваивания не зависит от того, к чему он назначен! Предположим, вы имели
short M(short x){...}
int M(int x){...}
short y = M(-1);
Я не думаю, что вы ожидаете, что разрешение перегрузки будет "хорошо, я бы выбрал M (int), потому что -1 - это int, но нет, я выберу M (короткий), потому что иначе назначение не будет работать". Разрешение перегрузки ничего не знает о том, где результат. Задача состоит в том, чтобы определить, какая правильная перегрузка основана на приведенных аргументах, а не на основе контекста вызова.
Определение типа условного выражения работает одинаково. Мы не смотрим на тип, к которому он идет, мы смотрим на типы, которые находятся в выражении.
ОК, поэтому мы установили, что тот факт, что это назначается короткому, не имеет значения для определения типа выражения. Но это все еще оставляет вопрос "Почему тип условного выражения int, а не короткий?"
Это очень хороший вопрос. Перейдите в спецификацию.
Второй и третий операнды, x и y, оператора?: управляют типом условного выражения.
Если тип X и y имеют тип Y, то:
Если между X и Y существует неявное преобразование, но не от Y до X, то Y является типом условного выражения.
Если неявное преобразование существует от Y до X, но не от X до Y, то X является типом условного выражения.
В противном случае тип выражения не может быть определен, и возникает ошибка времени компиляции.
В этом случае оба операнда имеют тип. (В формулировке о "если x есть тип..." для случая, когда у вас есть нуль или лямбда, у них нет типов!) Первый операнд имеет тип int, второй - тип короткий.
Неявное преобразование существует от short до int, но не от int до short. Поэтому тип условного выражения - int, который не может быть назначен короткому.
Теперь можно сказать, что этот алгоритм не так хорош, как мог бы быть. Мы могли бы значительно усложнить алгоритм для рассмотрения всех случаев, когда существовали два возможных типа "кандидата" - в этом случае int и short являются правдоподобными кандидатами, поскольку обе ветки могут быть конвертированы как в int, так и в short, когда рассматриваются как конкретные выражения, а не просто иметь типы. В этом случае мы могли бы сказать, что меньший из двух типов был предпочтительным типом.
(Иногда в С# мы говорим, что более общий из двух типов является лучшим типом, но в этом случае вы бы хотели, чтобы мы выбрали более конкретный язык. К сожалению, язык не соответствует конкретному аспекту дизайна, я лично скорее всего, мы всегда будем выбирать более конкретные, но есть сценарии ввода типа, где это было бы теперь нарушением изменений.)
Я подумал о том, чтобы сделать это еще в 2006 году. При разработке поведения того, как LINQ имеет дело с ситуациями, в которых есть несколько типов, и один должен быть выбран как "лучший", мы заметили, что условный оператор уже должен был решить эту проблему и, кроме того, в С# 2 она не была реализована в соответствии со спецификацией. Об этом шла долгая дискуссия, и мы в конечном итоге внесли некоторые незначительные изменения в спецификацию для условного оператора, чтобы привести ее в соответствие с ее реализованным (и желательным) поведением. Однако мы решили не брать более масштабное изменение настройки алгоритма, чтобы использовать меньший из двух возможных типов, когда их было несколько.
Для некоторых размышлений по этой проблеме см. мои сообщения за 2006 год: