为什么此If-AndAlso语句中的第二项要尽早评估?

Dan*_*l B 5 vb.net

为清楚起见,进行编辑:这是编译器的一个很奇怪的行为,我想问的是为什么它通常以这种方式表现,而不是如何解决(已经有几种简单的解决方案)。

最近,我遇到了一段代码,该代码引发了一个微妙的错误,并最终引发了异常。一个简短的,人为的示例:

Dim list As List(Of Integer) = Nothing
If list?.Any() AndAlso list.First() = 123 Then
    MessageBox.Show("OK then!")
End If
Run Code Online (Sandbox Code Playgroud)

在实际示例中,list只是偶尔出现Nothing,为了简化起见,我只是缩短了代码。If语句中第一项的用意是要检查列表是否不是Nothing,还要测试是否存在至少一个元素。由于在这种情况下listNothing,所以list?.Any()实际/通常求值为Nothing。有些违反直觉,第二项,即list.First() = 123同时运行时进行评估,从而导致一个明显的例外。这有点违反直觉,因为起初大多数人会认为这Nothing似乎是False,并且由于我们在AndAlso此处使用a ,因此短路运算符将阻止If执行语句的后半部分。

附加调查/“您尝试了什么:”

快速检查,以确认缩短If list?.Any() Then似乎对待list?.Any()False

Dim list As List(Of Integer) = Nothing

If list?.Any() Then
    MessageBox.Show("OK then!")   'This line doesn't get hit / run
End If
Run Code Online (Sandbox Code Playgroud)

此外,我们可以通过以下几种方法解决此问题:If list IsNot Nothing AndAlso list.Any() AndAlso list.First() = 123 Then可以正常运行,也可以If If(list?.Any(), False) AndAlso list.First() = 123 Then

由于VB.Net不是我常用的语言,所以我想在C#中看一下:

List<int> list = null;
if (list?.Any() && list.First() == 123)
{
    MessageBox.Show("OK then!");
}
Run Code Online (Sandbox Code Playgroud)

但是,这会导致编译错误:

error CS0019: Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'
Run Code Online (Sandbox Code Playgroud)

除了显而易见的事实,即更严格的编译器检查可以防止在C#方案中犯此错误,这使我相信VB.Net方案中正在发生类型强制。一种猜测可能是编译器正在尝试将第二项的布尔结果转换为可为空的布尔值,但是这对我来说并没有太大意义。具体来说,为什么它会在评估之前/与左侧相同的时间进行评估,并且像应该那样早放弃整个过程?回顾正常工作的VB.Net示例,所有示例都涉及显式检查,这些检查具有简单的布尔结果,而不是可为空的布尔值。

我希望有人能对此行为有一些深刻的了解!

TnT*_*nMn 2

这似乎是 VB 编译器语法评估中的一个遗漏(错误)。?的文档。和 ?() 空条件运算符 (Visual Basic)状态:

在执行成员访问 (?.) 或索引 (?()) 操作之前测试左侧操作数的值是否为 null(Nothing);如果左侧操作数的计算结果为 Nothing,则返回 Nothing。请注意,在通常返回值类型的表达式中,null 条件运算符返回 Nullable。

表达式list?.Any()( Enumerable.Any Method ) 通常会返回Boolean(ValueType),因此我们应该期望list?.Any()生成 Nullable(Of Boolean)。

我们应该看到编译器错误,因为 Nullable(Of Boolean) 不能参与AndAlso 运算符表达式。

有趣的是,如果我们将其视为list?.Any()Nullable(Of Boolean),它就会被视为有记录。

If (list?.Any()).HasValue() AndAlso list.First = 123 Then
 ' something
End If
Run Code Online (Sandbox Code Playgroud)

编辑:以上并没有真正解决你的原因?

如果反编译生成的 IL,您会得到如下内容:

 Dim source As List(Of Integer) = Nothing
 Dim nullable As Boolean?
 Dim nullable2 As Boolean? = nullable = If((Not source Is Nothing), New Boolean?(Enumerable.Any(Of Integer)(source)), Nothing)
 nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault), False, If((Enumerable.First(Of Integer)(source) Is &H7B), nullable, False))
 If nullable.GetValueOrDefault Then
    MessageBox.Show("OK then!")
 End If
Run Code Online (Sandbox Code Playgroud)

这显然不会编译,但如果我们稍微清理一下,问题的根源就会变得显而易见。

Dim list As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = If(list IsNot Nothing, 
        New Boolean?(Enumerable.Any(Of Integer)(list)),
        Nothing)

' nullable2 is nothing, so the 3rd line below is executed and throws the NRE

nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault),
        False,
        If((Enumerable.First(Of Integer)(list) = 123), nullable, False))

If nullable.GetValueOrDefault Then
    MessageBox.Show("OK then!")
End If
Run Code Online (Sandbox Code Playgroud)

编辑2:

OP 从可空值类型 (Visual Basic)的文档中找到了以下语句

使用短路求值的 AndAlso 和 OrElse 必须在第一个操作数求值为 Nothing 时求值第二个操作数。

Option Strict Off如果有效并且使用OrElse 运算符asNothing可以隐式转换为,则此语句有意义False。对于OrElse运算符,仅当第一个表达式为 时才计算第二个表达式True。对于AndAlso运算符,如果第一个表达式为 ,则不计算第二个运算符True

另外,请考虑以下带有Option Strict On.

Dim list As List(Of Integer) = Nothing
Dim booleanNullable As Nullable(Of Boolean) = list?.Any()
Dim b As Boolean = (booleanNullable AndAlso list.First() = 123)
If b Then
    ' do something
End If
Run Code Online (Sandbox Code Playgroud)

对原始逻辑的重新安排确实会产生编译器错误。 编译器错误

使用 时Option Strict Off,不会生成编译器错误,但会发生相同的运行时错误。

我的结论: 正如最初所说,这是一个错误。当AndAlso块中包含运算符时If-Then,编译器会使用类型转换松弛来处理 null 条件运算符的结果,Option Strict Off而不管 的实际状态如何Option Strict