【问题标题】:Casting with conditional/ternary ("?:") operator使用条件/三元 ("?:") 运算符进行强制转换
【发布时间】:2010-11-13 09:22:45
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我想澄清一下,在第一个评估为零而不是 DBNull.value。为什么将 0 转换为十进制会引发异常?

标签: c# conditional-operator


【解决方案1】:

更新:这个问题是the subject of my blog on May 27th 2010。谢谢你的好问题!

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

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 拆箱为十进制是非法的。至于原因,请参阅我关于该主题的博客文章:

Representation and Identity

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

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

但正如我们所见,事实并非如此

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

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

【讨论】:

  • 我不明白为什么编译器会在编译错误似乎更合适的地方插入类型转换。
  • @raj:您希望看到实施什么规则来确定何时给出编译错误?
  • 我相信编译器应该告诉我这条规则“语言规则不允许您在一个分支上返回对象而在另一个分支上返回 int”,而不是将 int 转换为 object 以使自己开心。如果编译器告诉他有什么问题,Loannis 会发现该语句的问题
  • @Raj:好的,那么编译器应该如何处理 b ? "" : null - 在一个分支上返回一个字符串,在另一个分支上返回一个无类型的空引用。编译器应该抱怨吗?由于一侧甚至没有类型,显然类型不相等。
  • @Matthew:你如何制作一个合法的 C# 程序,其中包含不使用值的条件表达式?
【解决方案2】:

不同之处在于编译器无法确定ObjectInt32之间良好匹配的数据类型。

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

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

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

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

【讨论】:

  • C# 规范:“装箱转换允许将值类型隐式转换为引用类型。装箱转换存在从任何非可空值类型到对象、到 System.ValueType 和到由不可空值类型实现的任何接口类型。此外,枚举类型可以转换为 System.Enum 类型。“因此在 Int32 和对象之间存在隐式装箱转换。如果编译器无法确定类型,它将在编译时而不是在运行时失败。请看我的回答...
【解决方案3】:

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

这是 C# 规范的摘录:

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

  • 如果 X 和 Y 是同一类型,则这是条件表达式的类型。
  • 否则,如果存在从 X 到 Y,但不存在从 Y 到 X 的隐式转换(第 6.1 节),则 Y 是 条件表达式。
  • 否则,如果存在从 Y 到 X 的隐式转换(第 6.1 节),而不是从 X 到 Y,则 X 是 条件表达式。
  • 否则无法确定表达式类型,并出现编译时错误。

【讨论】:

    【解决方案4】:

    你的行应该是:

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

    0m是零的十进制常数

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

    【讨论】:

    • 这很可能会编译成与第二行完全相同的代码。
    • 也许可以,但值得指出的是,零的十进制常数是什么
    【解决方案5】:

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

    【讨论】:

    • 数据库中的实际值与编译器无关。变量的类型是 Object,Double 值不会比 Int32 值匹配。
    • 别人说的就是0m
    • 十进制类型的文字零:0m。
    • 这不是编译器错误,而是运行时错误。数据库通常将十进制值作为双精度值返回,因此这是您尝试匹配的类型。
    • 但是,如果运行时类型是十进制,那么继续使用 M。:)
    【解决方案6】:

    除非我弄错了(这很有可能),否则它实际上是 0 导致异常,这取决于 .NET(疯狂地)假设文字的类型,因此您需要指定 0m 而不仅仅是 0。

    请参阅MSDN 了解更多信息。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

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

        至少,类似的情况转换为我的参数。

        【讨论】:

        • 这没有意义:因为你同时施放了两个分支,所以最初的施放是无用的。
        猜你喜欢
        • 2016-01-11
        • 2017-08-26
        • 1970-01-01
        • 2020-01-30
        • 2012-02-12
        • 2019-10-08
        • 1970-01-01
        • 1970-01-01
        • 2017-01-23
        相关资源
        最近更新 更多