С#?: Условный оператор

У меня есть этот исходный код С# 2.0:

object valueFromDatabase;
decimal result;
valueFromDatabase = DBNull.Value;

result = (decimal)(valueFromDatabase != DBNull.Value ? valueFromDatabase : 0);
result = (valueFromDatabase != DBNull.Value ? (decimal)valueFromDatabase : (decimal)0);

Первая оценка результатов бросает InvalidCastException, а вторая - нет. В чем разница между этими двумя?

Ответы

Ответ 1

UPDATE: этот вопрос был тема моего блога 27 мая 2010 года. Спасибо за отличный вопрос!

Здесь очень много непонятных ответов. Позвольте мне попытаться точно ответить на ваш вопрос. Давайте упростим это:

object value = whatever;
bool condition = something;
decimal result = (decimal)(condition ? value : 0);

Как компилятор интерпретирует последнюю строку? Проблема, с которой сталкивается компилятор, заключается в том, что тип условного выражения должен быть согласованным для обеих ветвей; правила языка не позволяют вам возвращать объект на одну ветку, а int - на другую. Выбор - это объект и int. Каждый int преобразуется в объект, но не каждый объект конвертируется в int, поэтому компилятор выбирает объект. Поэтому это то же самое, что

decimal result = (decimal)(condition ? (object)value : (object)0);

Следовательно, возвращаемый ноль - это вставка в штучной упаковке.

Затем вы удаляете int до десятичного. Недопустимо распаковывать коробчатый int до десятичного. По причинам, см. Мою статью в блоге по этому вопросу:

Представление и идентификация

В основном, ваша проблема заключается в том, что вы действуете так, как если бы распределение было равно десятичным, это было следующим:

decimal result = condition ? (decimal)value : (decimal)0;

Но, как мы видели, это не то, что

decimal result = (decimal)(condition ? value : 0);

означает. Это означает "сделать обе альтернативы объектам, а затем распаковать результирующий объект".

Ответ 2

Разница заключается в том, что компилятор не может определить тип данных, который является хорошим совпадением между Object и Int32.

Вы можете явно применить значение int к Object, чтобы получить тот же тип данных во втором и третьем операндах, чтобы он компилировался, но код couse означает, что вы боксируете и распаковываете значение:

result = (decimal)(valueFromDatabase != DBNull.value ? valueFromDatabase : (object)0);

Это будет компилироваться, но не запускаться. Вам нужно вставить десятичное значение в unbox как десятичное значение:

result = (decimal)(valueFromDatabase != DBNull.value ? valueFromDatabase : (object)0M);

Ответ 3

Тип оператора будет объектом, и в случае, если результат должен быть равен 0, он будет неявным образом помещен в коробку. Но 0 literal по умолчанию имеет тип int, поэтому вы вводите int. Но с явным приведением в десятичное число вы пытаетесь распаковать его, что не разрешено (размер в коробке должен сильно отличаться от того, на который вы возвращаете). Вот почему вы можете получить исключение.

Вот выдержка из спецификации С#:

Второй и третий операнды оператора?: управляют типом условного выражения. Пусть X и Y - типы второго и третьего операндов. Тогда,

  • Если X и Y являются одним и тем же типом, то это тип условного выражения.
  • В противном случае, если неявное преобразование (§6.1) существует от X до Y, но не от Y до X, то Y является типом условное выражение.
  • В противном случае, если неявное преобразование (§6.1) существует от Y до X, но не от X до Y, то X является типом условное выражение.
  • В противном случае тип выражения не может быть определен, и возникает ошибка времени компиляции.

Ответ 4

Ваша строка должна быть:

result = valueFromDatabase != DBNull.value ? (decimal)valueFromDatabase : 0m;

0m - десятичная константа для нуля

Обе части условного оператора должны оценивать один и тот же тип данных

Ответ 5

Для части x: y нужен общий тип, значение базы данных, вероятно, является некоторым видом float и 0 является int. Это происходит до того, как приведение будет десятичным. Попробуйте "0.0" или ": 0D".

Ответ 6

Если я ошибаюсь (что очень возможно), на самом деле это 0, вызывающее исключение, и это до .NET(безумно), предполагая тип литерала, поэтому вам нужно указать 0m, а не просто 0.

Подробнее см. MSDN.

Ответ 7

Для компилятора существуют два разных типа (во время компиляции), которые можно отнести к десятичной. Этого не может сделать.

Ответ 8

Ваш ответ будет работать, если вы комбинируете оба:

result = (decimal)(valueFromDatabase != DBNull.Value ? (decimal)valueFromDatabase : (decimal)0);

По крайней мере, аналогичная ситуация забрасывается в параметр для меня.