42

我有这个 C# 源代码的摘录:

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

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

第一个结果评估抛出一个InvalidCastException,而第二个没有。这两者有什么区别?

4

8 回答 8

102

更新:这个问题是我 2010 年 5 月 27 日博客的主题。谢谢你的好问题!

这里有很多非常令人困惑的答案。让我试着准确地回答你的问题。让我们简化一下:

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

编译器如何解释最后一行?编译器面临的问题是两个分支的条件表达式类型必须一致;语言规则不允许您在一个分支上返回 object 而在另一个分支上返回 int。选择是 object 和 int。每个 int 都可以转换为 object,但不是每个 object 都可以转换为 int,因此编译器选择 object。因此这与

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

因此,返回的零是一个装箱的 int。

然后,您将 int 拆箱为十进制。将装箱的 int 拆箱为十进制是非法的。至于原因,请参阅我关于该主题的博客文章:

表示和身份

基本上,您的问题是您的行为就像分配了十进制转换,如下所示:

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

但正如我们所见,这不是

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

方法。这意味着“将两种选择都放入对象中,然后将生成的对象拆箱”。

于 2009-07-23T15:51:31.403 回答
12

Object不同之处在于编译器无法确定与和之间良好匹配的数据类型Int32

您可以将int值显式转换object为以在第二个和第三个操作数中获得相同的数据类型,以便它编译,但是这意味着您正在对值进行装箱和拆箱:

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

这将编译,但不会运行。您必须将十进制值装箱才能取消装箱为十进制值:

result = (decimal)(valueFromDatabase != DBNull.value ? valueFromDatabase : (object)0M);
于 2009-07-23T13:37:50.237 回答
5

运算符的类型将是对象,如果结果必须为 0,它将被隐式装箱。但是 0 字面量默认为 int 类型,因此您可以将 int 装箱。但是通过显式转换为十进制,您尝试将其拆箱,这是不允许的(装箱类型必须与您转换回的类型非常相似)。这就是为什么你可以得到例外。

以下是 C# 规范的摘录:

?: 运算符的第二个和第三个操作数控制条件表达式的类型。设 X 和 Y 是第二个和第三个操作数的类型。然后,

  • 如果 X 和 Y 是相同的类型,那么这是条件表达式的类型。
  • 否则,如果存在从 X 到 Y 的隐式转换(第 6.1 节),而不是从 Y 到 X,则 Y 是条件表达式的类型。
  • 否则,如果存在从 Y 到 X 的隐式转换(第 6.1 节),而不是从 X 到 Y,则 X 是条件表达式的类型。
  • 否则,无法确定表达式类型,并出现编译时错误。
于 2009-07-23T13:38:25.587 回答
4

你的行应该是:

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

0m是零的十进制常数

条件运算符的两个部分都应评估为相同的数据类型

于 2009-07-23T13:42:00.293 回答
3

x : y 部分需要一个通用类型,数据库的值可能是某种浮点数,而 0 是一个整数。这发生在转换为十进制之前。尝试“:0.0”或“:0D”。

于 2009-07-23T13:37:59.703 回答
2

除非我弄错了(这很有可能)它实际上是 0 导致异常,这归结为 .NET(疯狂地)假设文字的类型,所以你需要指定 0m 而不仅仅是 0。

有关详细信息,请参阅MSDN

于 2009-07-23T13:39:15.727 回答
0

编译器有两种不同的类型来决定(在编译时)将哪一种转换为十进制。这是它做不到的。

于 2009-07-23T13:41:08.593 回答
-1

如果您将两者结合起来,您的答案将起作用:

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

至少,类似的情况对我来说是一个参数。

于 2012-01-13T15:34:29.477 回答