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

У меня есть этот исходный код C # 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 а вторая – нет. В чем разница между этими двумя?

ОБНОВЛЕНИЕ: Этот вопрос был предметом моего блога 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); 

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

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

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

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

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

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

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

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

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

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

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

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

0m – десятичная постоянная для нуля

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

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

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

Дополнительную информацию см. В MSDN .

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

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

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

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