为什么要对这个If-AndAlso语句中的第二项进行早期评估?

时间:2018-12-08 15:25:52

标签: vb.net

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

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

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

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

此外,我们可以通过以下几种方法来解决此问题:If list IsNot Nothing AndAlso list.Any() AndAlso list.First() = 123 ThenIf 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!");
}

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

error CS0019: Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'

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

我希望有人能对此行为提供一些很好的见识!

3 个答案:

答案 0 :(得分:2)

这似乎是VB编译器的语法评估中的遗漏(错误)。 ?. and ?() null-conditional operators (Visual Basic)的文档说明:

  

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

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

我们应该看到编译器错误,因为Nullable(Of Boolean)无法参与AndAlso Operator表达式。

有趣的是,如果我们将list?.Any()视为Nullable(布尔值),则会视为已记录。

If (list?.Any()).HasValue() AndAlso list.First = 123 Then
 ' something
End If

编辑:以上内容并未真正解决您的为什么?

如果对生成的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

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

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

Edit2:

OP从Nullable Value Types (Visual Basic)

的文档中发现了以下语句
  

使用短路评估的AndAlso和OrElse必须评估   他们的第二个操作数,当第一个取值为Nothing时。

如果Option Strict Off有效,并且将OrElse Operator作为Nothing的使用可以隐式转换为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

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

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

我的结论:如最初所述,这是一个错误。当AndAlso块中包含If-Then运算符时,无论Option Strict Off的实际状态如何,编译器都会使用Option Strict类型转换弛豫来处理空条件运算符的结果。 / p>

答案 1 :(得分:0)

部分原因在于其对数据库访问的强大支持,传统的VB支持Null值作为类型系统的一流组成部分。所有内置逻辑运算符的真值表中都包含空值(例如,请参见https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/imp-operator)。

由于这个原因,(如您稍后所述)对于没有值的Boolean?,VB选择使用与经典Null类似的语义也就不足为奇了。

实际上,解决此问题的最佳方法可能是:

If (list?.Any()).GetValueOrDefault() AndAlso list.First() = 123 Then

或者,您可以使用FirstOrDefault来避免首先需要检查Any,尽管这确实取决于您寻求的值不是默认值(或使用“魔术”替换值,请记住,您可以在GetValueOrDefault中覆盖默认值,例如

If list?.FirstOrDefault().GetValueOrDefault() = 123 Then

答案 2 :(得分:-2)

请尝试

if (list?.Any() IsNot Nothing AndAlso list.First() == 123)