VBA中的“And”运算符会在第一个参数为假时评估第二个参数吗?

52
Function Foo(thiscell As Range) As Boolean
  Foo = thiscell.hasFormula And (InStr(1, UCase(Split(thiscell.formula, Chr(40))(0)), "bar") > 0)
End Function

这个函数的存在是为了测试是否存在某个子字符串(在此例中为“bar”)位于一对括号之前。

我遇到麻烦的情况是,当传入函数的单元格为空时,thisCell.hasFormula为false,但and后面的语句仍然被评估。这会导致运行时出现下标超出范围的错误。

VBA实际上是否继续评估And的第二个参数,即使第一个参数为false?


5
请注意,VBA的And运算符不会短路,因为它是一个按位运算符,而不是逻辑运算符。参见:https://dev59.com/clzUa4cB1Zd3GeqP8f6f#8047021 - jtolle
4
@jtolle的说法不正确——如果参数是布尔值,它将返回一个布尔值,因此它支持位运算和逻辑运算。 (当然,你可以认为逻辑运算是使用1位整数的位运算的特殊情况,但重点是如果微软公司选择,它本来可以支持短路运算。) - Hugh Allen
@Hugh,有趣。一直以来我都认为“And”只是一个位运算符,尽管它模拟了逻辑运算,因为“True = -1”和“False = 0”。但你说得对,“And”确实是一个逻辑运算符,如果传递给它的两个表达式都是布尔值。如果一个或两个操作数是数字,则它只是按位运算。但我想它不能短路,因为必须评估两个表达式,以确保其中一个或两个不是数字而不是布尔值。所以我认为“按位性”在这里仍然导致没有短路。 - jtolle
@jtolle 另一个 VBA 支持的功能是强类型。使用变量是可选的。如果逻辑运算符的参数在编译时已知为布尔值,则仍然支持短路计算。即使对于整数,如果 Or 的左侧参数是 "all 1s" (&HFFFFFFFF-1&),则右侧参数也可以被跳过,同样适用于 And 如果左侧参数为 0。 - Hugh Allen
@Hugh,这当然都是真的。但可能会有点令人困惑。我不知道其他语言是否有短路位运算符。此外,VBA可能试图保持与旧版BASIC的向后兼容性。最好像微软最终为VB.NET所做的那样添加新的运算符。(VBA的基本祖先在其他地方也有体现,例如我喜欢的其中一个链接: https://dev59.com/s3NA5IYBdhLWcg3wKaYx#5554208) - jtolle
7个回答

64
你需要的是 "短路求值",VBA不支持该功能。你可以在这里看到一种可能适用于你情况的方法。该方法涉及将If替换为Select Case。还有一个使用嵌套Ifs的示例。

81
当我使用VBA工作时,感觉就像是用蜡笔画蒙娜丽莎。 - James
1
确实。太遗憾了,你不能使用C# :( - Kapé

12

正如DOK所提到的:不,VBA没有短路求值。

使用2个If-Then语句比使用AND运算符在技术上更有效率,但除非你需要频繁使用,否则你不会注意到任何节省,因此选择更易读的方式。如果你想要变得更加技术化,VBA处理多个If-Then语句比处理Select Case语句更快。

VBA有些古怪 :)


2
答案是肯定的,VBA不会短路评估。
这不仅仅是一个风格问题;在类似以下情况中,这将有很大的区别:
If i <= UBound(Arr, 1) And j <= UBound(Arr, 2) And Arr(i, 1) <= UBound(Arr2, 1) Then
    Arr2(Arr(i, 1), j) = Arr(i, j)
End If

...这是不正确的。更恰当地说:

If i <= UBound(Arr, 1) And j <= UBound(Arr, 2) Then
    If Arr(i, 1) <= UBound(Arr2, 1) Then
        Arr2(Arr(i, 1), j) = Arr(i, j)
    End If
End If

或者,如果您不喜欢嵌套的if语句:

If i > UBound(Arr, 1) Or j > UBound(Arr, 2) Then
    ' Do Nothing
ElseIf Arr(i, 1) > UBound(Arr2, 1) Then
    ' Do Nothing
Else
    Arr2(Arr(i, 1), j) = Arr(i, j)
End If

2
VBA确实有一种类似短路的行为。通常情况下,Null会在表达式中传播,例如3 + NullNullTrue And NullNull。然而:

? False And Null
False

这看起来像是短路行为 - 到底发生了什么?当合取(And)的另一个参数为False0时,Null不会传播 - 结果只是False0。左边或右边的参数都无所谓。如果析取(Or)的另一个参数为True或非零整数(浮点值将使用此规则四舍五入为整数),也是同样的道理。

因此,在传递给 AndOr 的参数中无法预防副作用和错误,但可以“短路” Null 传播。这种行为似乎继承自 SQL

嗯,我有点迷惑了,你能否添加一个A和B条件的示例来说明它是如何工作的? - Enissay
@Enissay,我的两个示例都使用了 And。也许你能更好地解释一下你所困惑的是什么? - Hugh Allen
2
这与短路行为无关。两个表达式被评估,第一个返回“False”,第二个返回“True”,这里没有短路。然后VBA进行一些奇怪的转换,并不一致地返回“False”或“Null”。 - stenci

1

由于答案是谷歌排名靠前的之一,只需要搜索类似于 vba if condition not lazy 的东西,我想提供一个更简单的例子,解决方案和问题都包括两种条件:AND 和更有趣的 OR ...

Dim cond1 As Boolean   'some 1st condition that may be True or False
Dim obj As Collection  'just some sample object that may or may not be instantiated

(²:如果其他开发人员不知道背景,我觉得更好的方法是解释你为什么没有选择OR)


AND

案例
cond1 = False
If cond1 Then Set obj = New Collection

问题:
If cond1 And obj.Count > 0 Then Debug.Print "Count > 0!"  'throws error if < cond1 = False > 
                                                          'because condition 2 is always evaluated

解决方案:
If cond1 Then If obj.Count > 0 Then Debug.Print "Count > 0!"  'AND would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572

根据口味、复杂度和可读性,写成这样可能更有意义:

If cond1 Then
    If obj.Count > 0 Then  'AND would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
        Debug.Print "Count > 0!"
    End If
End If

OR

情况
 cond1 = True
 If Not cond1 Then Set obj = New Collection  'obj stays < Nothing > otherwise

问题:
 If cond1 Or obj.Count = 0 Then Debug.Print "no objects!"  'throws error if < cond1 = True >
                                                           'because condition 2 is always evaluated
解决方案1: 使用 Select,在原地进行操作,不冗余,一行代码,无需使用 GoTo
 Select Case True:  Case cond1, obj.Count = 0:  Debug.Print "no objects!":  End Select  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572

如果需要跨越多行并且还有其他内容:

 Select Case True
     Case cond1, obj.Count = 0  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
         Debug.Print "no objects!"
     Case Else
         Debug.Print "object count: " & obj.Count
 End Select

解决方案2:

就地修改,不冗余的代码,最小化使用GoTo,但使用更多的长If多行代码:

 If cond1 Then
 noObjs:
     Debug.Print "no objects!"
 ElseIf obj.Count = 0 Then  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
     GoTo noObjs
 End If

解决方案3:

原地修改,在一行上使用类似于OR连接的条件(可能适用),并且使用了相当多的GoTo

 If cond1 Then GoTo noObjs ElseIf obj.Count = 0 Then GoTo noObjs  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
 GoTo skipOnAllFalse
 noObjs:
     Debug.Print "no objects!"

 skipOnAllFalse:    'use more specific label/scenario name if possible

解决方案4:

避免使用GoTo,将条件(如果适用)放在一行上,但模块/类代码可能更难以阅读/散乱/混乱。

 Private Sub noObjs():  Debug.Print "no objects!"

 If cond1 Then noObjs ElseIf obj.Count = 0 Then noObjs  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
解答:

解决方案 5:

使用一个条件变量:

 Dim any As Boolean:  any = cond1
 If Not any Then any = obj.Count = 0  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
 If any Then Debug.Print "no objects!"

解决方案6:
使用多个条件变量:
 Dim c1 As Boolean:  Dim c2 As Boolean
 c1 = cond1
 If Not c1 Then c2 = obj.Count = 0  'OR would not short-cicuit!² https://dev59.com/7Ww05IYBdhLWcg3w0U8O#57521572
 If c1 Or c2 Then Debug.Print "no objects!"  'safe to use Or now

0

我认为这是最佳实践:

sub my conditions()
        If Condition1=constraint1 then
         if Condition2=constraint2 then
          if condition3=constraint3 then
           ...
            ....
        end if
         end if
          end if
    else
      end if
           ....
    end if
end sub

因此,只有在满足条件 i 的情况下,你才会通过条件。


5年前,OP问了一个问题,即在条件语句中,如果第一个条件为假,'and'关键字是否会“短路”。你是在回答这个问题吗? - ppovoski
@ppovoski 是的,我确实是。 - Moreno

-1
考虑要运行的机器代码。 最快的应该是类似于以下代码的混合体...
如果 sfsf,则转到 SkipAB
如果 fdf,则转到 goneBad
如果 dffdefedwf,则转到 MustHave SkipAB: 如果 dsda > 4,则转到 MustHave GoneBad: 退出函数
MustHave: ThisIS = true 当程序需要多次运行时,仅在文件搜索大型驱动器或使用简单的布尔测试跳过耗时函数(例如查找关闭工作表中的所有工作表和名称)时才能节省几个时刻。
     If Not wFF.UsingFileExtMatch Then GoTo SkipExt
                If Not wFF.OKFileEXTMatch Then GoTo BADFile

SkipExt: 如果wFF.UsingFileNameMatch为假,则跳过文件名匹配 如果wFF.OKFileNameMatch为假,则转到BADFile SkipFileMatch: 如果wFF.UsingDaysAgo为假,则跳过天数匹配 如果wFF.OKDaysAgo为假,则转到BADFile SkipDaysAgo:

[/code]


我认为这只是其他答案的一个版本,用GoTo替换了嵌套的If。由于在如此简单的情况下使用GoTo被认为是不好的编程风格,因此这个答案有点超出范围。 - Andreas Covidiot

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