三元运算符VB与C#:为什么将“Nothing”解析为零?

30
我刚刚搞砸了自己,想知道是否有实际原因导致这种情况的发生。不管怎样,这个问题可以留下来方便未来遇到同样问题的人参考。
假设我们在 VB.NET 中有一个可空值:
Dim i as Integer?

我们想根据条件使用三元运算符给它赋值,因为这样很整洁:

i = If(condition(), Nothing, 42)

也就是说,如果条件为true,则使用nullability,否则使用该值。
在这一点上,问题就出现了。VB编译器不知何故决定NothingInteger的共同基本类型是Integer,然后悄悄地将语句翻译成:

i = If(condition(), 0, 42)
现在,如果您想使用C#完成这个操作:
i = (condition()) ? null : 42;

你会立即收到编译器错误,提示<null>不能与int混合使用。这很棒,因为如果我按照C#的方式做,我的脚可能会更加健康。为了编译通过,你必须显式地写出:

i = (condition()) ? null : (int?)42;
现在,您可以在 VB 中执行相同的操作,并获得您所期望的正确的 null 值:
i = If(condition(), Nothing, CType(42, Integer?))

但这需要你先将脚打穿。没有编译器错误和警告。这是使用Explicit OnStrict On的情况。


所以我的问题是,为什么?
我应该把这视为编译器的一个错误吗?
还是有人可以解释一下为什么编译器会这样做?


8
因为你正在发现,VB 是一种可怕的语言,它会在你没有要求的情况下做一些随意的事情,试图帮忙但却误入歧途。 - cdhowie
12
除非你已经尝试了超过几周,否则不要否定它。VB.Net 已经从 VB6 发展了很远。 - Joel Coehoorn
9
任何没有学习新语言特点的人都会发现这门语言很令人沮丧。谴责“语言”本身导致沮丧的人需要重新考虑他们的反应。 - MarkJ
5
@Alex Nah,我是那种使用过很多不同编程语言的人之一。我从C64 BASIC开始编程,然后转向VB1,最终使用VB4、5、6和.NET。这并不意味着我不了解VB,它也是我的语言,就像其他人一样。我已经忍受了它的问题很长时间,知道我为什么不喜欢它。请注意,我没有改变原文意思。 - cdhowie
3
在处理可空整数时,我更喜欢使用new Integer?而不是不写任何东西。这样更冗长,但可以消除歧义。 - Jonathan Allen
显示剩余5条评论
7个回答

37
这是因为VB的 Nothing 与C#的 null 没有直接等效.
例如,在C#中,以下代码不会编译:
int i = null;

但是这段VB.Net代码运行良好:
Dim i As Integer = Nothing

VB.Net中的Nothing实际上更接近于C#中的default(T)表达式。


是的,那是个打字错误。现在已经修复了。 - Joel Coehoorn
2
哎呀,我有几秒钟以为我得修复很多代码! - Jon B
这就解释了为什么 int i = null; 不起作用,但是如果 int? i = 0;int? i = null 都可以工作,那么为什么 int? i = condition()? null : 0; 就不行呢? - Jon B
1
@Jon 我无法回答那个问题,但他在询问有关vb代码的问题。将vb转换为c#的翻译更像是 int? i = condition()?default(int):0; - Joel Coehoorn
好的,我完全忽略了一个事实,就是你可以通过将值类型设置为“Nothing”来使用default()。谢谢,现在我明白了。 - GSerg
1
@JonB 实际上,我现在可以回答这个问题了。condition()?null:0 表达式必须独立存在;赋值的目标并不重要。当编译器推断该表达式的类型时,未经类型推断的 null0 操作数显然是不兼容的,因为 int? 仍然是一个值类型,并且还有其他的“魔法”使你可以将 null 赋值给它。如果编译器能够聪明地选择 int?,那就太好了,但它还不能这样做。 - Joel Coehoorn

12

三元运算符只能返回一种类型。

在C#中,它会根据null42来选择类型。由于null没有类型,所以它决定三元运算符的返回类型是42的类型;一个普通的int。然后它抱怨说因为你不能将null作为普通的int返回。当您将42强制转换为int?时,三元运算符将返回一个int?,因此null是有效的。

现在,我不知道VB怎么样,但引用MSDN的话:
将Nothing分配给变量将其设置为其声明类型的默认值。

由于VB确定三元运算符将返回一个int(使用与C#相同的过程),所以Nothing0。再次将42强制转换为int?会将Nothing转换为int?的默认值,即null,就像您期望的那样。


2

Nothingnull不是同一件事情...来自MSDN:

Nothing分配给变量会将其设置为已声明类型的默认值。

此外

如果在表达式中提供了值类型,则IsNothing始终返回False。

请记住,int?是可空类型,但它仍然是值类型,而不是引用类型。

尝试将其设置为DbNull.Value而不是Nothing...


2

我认为这与IF有关,而不是与Nothing有关。请考虑以下代码:

''#     This raises an exception
Dim x As Integer?
x = If(True, Nothing, Nothing)
MessageBox.Show(x.Value)

''#     As does 
Dim x As Integer?
x = Nothing
MessageBox.Show(x.Value)

''#     Changing one of the truthpart arguments of If is what seems to return the zero.
Dim x As Integer?
x = If(True, Nothing, 5)
MessageBox.Show(x.Value)

我仍然不知道为什么会这样,可能是向VB团队提问的问题。我认为这与Nothing关键字或Nullable无关。


这与“Nothing”是关键字而不是值有关。正如被接受的答案所指出的那样,“Nothing”实际上意味着“Default(T)”。因此,在您的第一个示例中,T被评估为Integer?在第二个示例中,它被评估为Integer。这与您的结果相匹配。 - jmoreno

1
在许多情况下,Nothing将被转换为默认值。要像使用null一样使用Nothing,您需要将其强制转换为正确的可空类型。
Dim str As String
Dim int As Nullable(Of Integer) ' or use As Integer?
Dim reader As SqlDataReader
Dim colA As Integer = reader.GetOrdinal("colA")
Dim colB As Integer = reader.GetOrdinal("colB")
str = If(reader.IsDBNull(colA), DirectCast(Nothing, String), reader.GetString(colA))
int = If(reader.IsDBNull(colB), DirectCast(Nothing, Nullable(Of Integer)), reader.GetInt32(colB))

-1
这是因为Integer不是引用类型。'Nothing'只适用于引用类型。对于值类型,将Nothing赋值会自动转换为默认值,在整数的情况下为0。

-1

在VS2015中(至少)使用New Integer?现在实际上是可能的。

Ex.:

如果(testInt>0, testInt, New Integer?),其中testInt的类型为Integer?。

testInt 的类型为 Integer?,这意味着整个表达式也是 Integer?,不需要使用 New Integer?。这种方式在 VS2015 之前就已经存在了,并且与问题无关。 - GSerg

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