有条件的运算符?:与可空类型转换

20

根据MSDN文档,以下两个代码片段是等价的:

bool value;
int x = (value) ? 0 : 1;

bool value;
int x;
if (value)
    x = 0;
else
    x = 1;
很棒,非常好用。简洁高效。 如果我们尝试对可空类型进行这样的操作:
int? x = (value.HasValue) ? value.Value : null;

我们会得到一个编译时错误:

The type of conditional expression cannot be determined
because there is no implicit conversion between '{NullableType}' and null.

这个可以成功编译:

int? value;
int? x;

if (value.HasValue)
    x = value.Value;
else
    x = null;

我理解编译器要求在第一条语句中使用显式转换 (int?)null。但我不明白的是为什么在 If Else 块中不需要。


可能是重复问题:https://dev59.com/MXVC5IYBdhLWcg3wbglT - Nick Gotch
1
@NickGotch 我看到了你的帖子,但是我对答案并不满意。我想要理解为什么在使用一种语法时需要强制转换,而在使用另一种语法时则不需要。 - Michael
1
我认为在这种情况下,你可以直接使用 x = value,或者我错了吗? - Alvin Wong
5个回答

27
< p > null 可以表示任何基于对象的数据类型。您需要将null 强制转换为特定的数据类型,以便它知道您在谈论什么。

< p >null 可以代表任何基于对象的数据类型。你需要将null 强制转换成一个数据类型,这样它就知道你在说什么了。

int? x = (value.HasValue) ? value.Value : (int?)null;

我知道,这听起来有点奇怪。


回答评论中的问题:

为什么它不是隐式的?
是的,我懂了。但为什么我不必将其转换为If Else块?

让我们一步一步地看代码。

你的else语句看起来像这样:

else x = null;
这意味着你正在将 null 值赋给 x。这是有效的,因为 x 是一个可以接受 nullsint? 类型。
区别出现在三元运算符上。它表示:“将运算符的值分配给 x”。问题(以及出错的原因)在于,三元运算符的结果是什么数据类型?
从你的代码来看,你无法确定,编译器也无法判断。
int? x = (value.HasValue) ? value.Value : null;
// int?        bool             int        ??

null 是什么数据类型?你很快就会说,“嗯,它是一个 int?,因为另一边是一个 int,结果是一个 int?。” 问题是,那以下情况怎么办:

string a = null;
bool? b = null;
SqlConnectionStringBuilder s = null;

这也是有效的,这意味着null可以用于任何基于对象的数据类型。这就是为什么您必须显式将null强制转换为要使用的类型,因为它可以用于任何类型!


另一个解释(可能更准确):

可空值和非可空值之间不能进行隐式转换。

int不可为空(它是结构),而null是可为空的。这就是为什么在Habib的答案中,您可以将强制转换放在左侧或右侧的原因。


为什么它不是隐式的呢? - Tharwen
是的,我明白。但为什么在 If Else 块中不需要进行类型转换呢? - Michael
@gunr2171 啊哈!是的,现在有意义了。只是为了澄清; ?: 操作符期望所有3个操作数都是单一的数据类型,对吗?或者至少是可以转换的类型。 - Michael
@Michael,有点像。根据msdn的说法,“条件”需要一个布尔值:第一个表达式和第二个表达式的类型必须相同,或者存在从一种类型到另一种类型的隐式转换。”(null`与其他类型没有隐式转换)。 - gunr2171

11

根据条件运算符MSDN的说明:

第一个表达式和第二个表达式必须是相同的类型,或者存在从一种类型到另一种类型的隐式转换

因此,在您的情况下,您的第一个表达式和第二个表达式为:

int? x = (value.HasValue) ? value.Value : null;
                             ^^^^^^^^      ^^^^^
                             first exp     2nd Exp

现在如果您看一下,您的第一个表达式是int类型,而第二个表达式是null,两者不同且没有隐式转换。因此,将它们中的任何一个强制转换为`int?`即可解决问题。

所以:

int? x = (value.HasValue) ? (int?) value.Value : null;
或者
int? x = (value.HasValue) ? value.Value : (int?) null;

都可以。

现在为什么使用if-else不需要分号呢?因为涉及到多个语句而不是一个单独的语句赋值。


6
var x = value.HasValue ? value.Value : default(int?);

也可以工作。


1

条件运算符 ?: 的文档指出,表达式 b ? x : y 的类型是通过检查 x 和 y 的类型来确定的:

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

在你的例子中

int? x = (value.HasValue) ? value.Value : null;

int和null之间没有隐式转换,因此最后一条适用。


我认为这是最好的答案,尽管为什么不使用“b”来确定类型转换呢? - MirroredFate

0
原因是条件表达式的类型由条件运算符 (?:) 的第二个和第三个运算符确定。
由于 null 没有类型,编译器无法确定整个表达式的类型,因此会发出编译器错误。
使用简单赋值运算符 (=) 的原因是操作符左侧确定了类型。由于在 If 语句中,x 的类型已知,因此编译器不会报错。
有关进一步解释,请参见第 7.14 节(条件运算符)和第 7.17.1 节(简单赋值)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接