Зачем мне приписывать int в тройном выражении?

Возможный дубликат:
Условный оператор не может использовать неявно?

Я столкнулся с особой ситуацией и хочу знать, почему я должен это делать. Я использую .NET 3.5.

Это работает:

short foo;

if (isValid)
    foo = -1;
else
    foo = getFoo();

Это не работает:

short foo;
foo = isValid ? -1 : getFoo();

Мне нужно typecast -1:

short foo;
foo = isValid ? (short)-1 : getFoo();

Что делает тройное выражение по-разному? Он считает, что -1 - это int, который нужно перевести в короткий. Но почему?

Ответ 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 год:

Ответ 2

Потому что -1 по умолчанию является целым числом. Для компилятора гораздо безопаснее сказать вам, что вам нужно явно указать, что делать, чем это делается для того, чтобы компилятор сделал предположение о том, что вы хотите сделать.

Вы, например, довольно прямолинейны. С небольшой дополнительной работой компилятор, очевидно, мог видеть, что вы хотите, чтобы -1 был коротким. Есть все краевые случаи, которые идут вместе с неявным преобразованием, которое не так просто. Если вы добавите правило для компилятора, чтобы сделать предположение о том, что вы хотите, вы должны применить его ко всем случаям не только к одному, и это затруднительно.

В качестве примечания вы должны проверить блог Эрика Липперта, поскольку я знаю, что он объясняет, почему компилятор не делает таких предположений.

Ответ 3

Условный оператор, применяемый к int и a short, имеет тип int; компилятор не выведет тип выражения из того типа, который вы ему назначили.

Вы не можете косвенно применить это выражение short к int.

Ответ 4

Условный оператор налагает, что оба возможных выражения результата одного типа. В этом случае левая сторона является int, а правая сторона - short, возвращаемая методом getPoo. Поскольку он всегда безопасен для преобразования short в int, компилятор выбирает, что результатом операции будет int.

Таким образом, результатом будет присвоение int a short, и поэтому вам нужно явно указать его на короткий.

Если вы явно используете подход if/else, вам будет присваиваться буквальное целое число, которое позволяет компилятору проверять, чтобы целое число буквально было присвоено короткому, без необходимости явного приведения.

Для внутреннего объяснения взгляните на:

Операторы роли не подчиняются закону о дистрибутиве