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

Еще один интересный вопрос со 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, что мы и получаем в этом случае.

Оригинал статьи