Операторы преобразования не подчиняются дистрибутивному закону
Еще один интересный вопрос со StackOverflow. Давайте рассмотрим следующую неприятную ситуацию:
object result;
bool isDecimal = GetAmount(out result);
decimal amount = (decimal)(isDecimal ? result : 0);
Разработчик, написавший этот код, будет весьма удивлен, обнаружив, что этот код скомпилируется и в случае выполнения альтернативного пути сгенерирует исключение “invalid cast exception”.
Кто-нибудь скажет почему?
В обыкновенной математике умножение является «дистрибутивным» по отношению к сложению. Т.е. q * (r + s) – эквивалентно q * r + q * s. В этом примере разработчик видимо ожидает, что оператор преобразования типа дистрибутивен по отношению к условному оператору. Но это не так. Приведенный код не эквивалентен следующему:
decimal amount = isDecimal ? (decimal)result : (decimal)0;
который, на самом деле, является корректным. Или еще лучше:
decimal amount = isDecimal ? (decimal)result : 0.0m;
Компилятор сталкивается с проблемой, которая связана с тем, что тип условного оператора должен быть совместимым в обеих ветках; правила языка не позволяют вам возвращать object в одной ветке и int – в другой.
Мы выбираем лучший тип на основе типов в самом выражении, а не на основе типов вне выражения, например, таких как операторы преобразования типов. Таким образом, выбор стоит между типами object и int. Любой int может быть преобразован к object, но не каждый object может быть преобразован в int, поэтому компилятор выбирает тип object. Т.е. ранее приведенный код эквивалентен следующему:
decimal amount = (decimal)(isDecimal ? result : (object)0);
Т.е. 0 возвращается, как упакованный int. Оператор преобразования типов затем распаковывает упакованный int в decimal. А как мы уже детально обсуждали, распаковывание упакованного int в decimal некорректно. Такой код приводит к генерации invalid cast exception, что мы и получаем в этом случае.