Просто чтобы добавить немного правильных ответов, опубликованных здесь: есть две рекомендации по разработке, которые приводят к этой спецификации.
Во-первых, мы рассуждаем "изнутри наружу". Когда вы говорите
double x = 2 + y;
сначала мы определяем тип x, затем тип 2, затем тип y, затем тип (2 + y) и, наконец, выясняем, совместимы ли x и (2 + y) типы. Но мы НЕ используем тип x при определении типа 2, y или 2 + y.
Причина, по которой это хорошее правило, заключается в том, что часто тип "приемника" - это именно то, что мы пытаемся выработать:
void M(Foo f) {}
void M(Bar b) {}
...
M(x ? y : z);
Что нам здесь делать? Мы должны определить тип условного выражения, чтобы выполнить разрешение перегрузки, чтобы определить, идет ли это к Foo или Bar. Следовательно, мы не можем использовать тот факт, что это, скажем, идет к Фу в нашем анализе типа условного выражения! Это проблема курицы и яйца.
Исключением из этого правила являются лямбда-выражения, которые do берут свой тип из своего контекста. Заставить эту функцию работать безумно сложно; см. мою серию блогов о лямбда-выражениях против анонимных методов, если вам интересно.
Второй элемент заключается в том, что мы никогда не "придумываем" тип для вас. Когда нам дается куча вещей, из которых мы должны вывести тип, мы всегда выводим тип, который на самом деле был прямо перед нами.
В вашем примере анализ выглядит так:
- определить тип следствия
- отработать тип альтернативы
- найдите лучший тип, который совместим как с последствиями, так и с альтернативой
- убедитесь, что происходит преобразование типа условного выражения в тип вещи, использующей условное выражение.
В соответствии с первым пунктом мы не рассуждаем снаружи внутрь ; мы не используем тот факт, что мы знаем тип переменной, к которой мы собираемся, чтобы определить тип выражения. Но самое интересное сейчас, когда у вас есть
b ? new Cat() : new Dog()
мы говорим "тип условного выражения - лучший тип в наборе {Cat, Dog}". Мы НЕ говорим, что «тип условного выражения - лучший тип, совместимый как с Cat, так и с Dog». Это было бы Млекопитающим, но мы этого не делаем. Вместо этого мы говорим «результат должен быть тем, что мы действительно видели», и из этих двух вариантов ни один не является явным победителем. Если бы вы сказали
b ? (Animal) (new Cat()) : new Dog()
тогда у нас есть выбор между Animal и Dog, и Animal - явный победитель.
Теперь обратите внимание, что мы на самом деле неправильно реализуем спецификацию C # при выполнении этого анализа типов! Подробнее об ошибке см. Мою статью об этом.