在VBA中的AndAlso/OrElse

49

我正在尝试通过以下方式在Excel宏中使用“And”实现惰性求值:

If Not myObject Is Nothing *And* myObject.test() Then
    'do something'
Else
    'do something else'
End If

我知道VB.NET中存在惰性求值,如AndAlsoOrElse,但在VBA中找不到类似的东西。如果VBA中不存在惰性求值,那么结构化代码的最佳方法是什么,以便它按照我的期望进行评估?


9
VBA 和 VB6 一样,都不具备短路求值的功能。 - Mitch Wheat
谢谢,我解决了我的问题,但我仍然好奇如何处理这种情况。我不会感到惊讶如果我错过了一些非常明显的东西,但我找不到一个好的方法来做到这一点,而不需要重写代码,即在两个不同的else情况下使用相同的代码。 - Luis
10个回答

56

唯一的短路(某种程度上)发生在Case表达式求值中,因此下面的笨拙语句实现了我认为你想要的功能;

->

Case表达式求值中,存在一种特殊的短路机制。因此,下面这个冗长的语句会实现你所需求的功能:

Select Case True
    Case (myObject Is Nothing), Not myObject.test()
        MsgBox "no instance or test == false"
    Case Else
        MsgBox "got instance & test == true"
    End Select
End Sub

为了清晰起见,在Case语句中的逗号有点像“OrElse”,但它从左到右检查条件,而不是从右到左。 - Wildhorn
7
@Wildhorn说:“而不是从右到左”——这听起来好像在VB.NET中的OrElse会从右到左评估条件。这是你的意思吗?因为我认为事实并非如此。 - ElRudi

16

这是一个老问题,但是这个问题仍然存在。我使用过的一个解决方法是:

Dim success As Boolean       ' False by default.

If myObj Is Nothing Then     ' Object is nothing, success = False already, do nothing.
ElseIf Not myObj.test() Then ' Test failed, success = False already, do nothing.
Else: success = True         ' Object is not nothing and test passed.
End If

If success Then
    ' Do stuff...
Else
    ' Do other stuff...
End If

这基本上是颠倒了原问题的逻辑,但得到了相同的结果。我认为这比其他仅使用If语句的解决方案更加简洁。使用Select语句的解决方案很聪明,但如果你想要只使用If语句的替代方案,我认为这是一个可以使用的方案。


警告:ElseIf在Word 2016 VBA中无法正常工作。 - CharlesW
@CharlesW ElseIf 是 VBA 的一部分,而不是宿主程序的一部分。它可以在任何宿主程序中使用,包括所有版本的 Word。你可能犯了其他错误。 - HackSlash
同意!- 感谢您的纠正! - CharlesW

3
或者你可以创建一个函数,将对象作为参数传递并返回布尔值。这是我通常采用的方法。
例如:
if Proceed(objMyAwesomeObject) then
       'do some really neat stuff here
else
       'do something else, eh
end if
...
end sub

private function Proceed(objMyAwesomeObject as Object)
     if not objMyAweseomeObject is nothing then
            Proceed = true
     elseif objMyAwesomeObject.SomeProperty = SomeValue then
            Proceed = true
     else
            Proceed = false
     endif
end function

1
我认为这不会起作用。如果 Not objMyAwesomeObject Is Nothing 评估为 True,那么第一个 ElseIf 语句将永远不会执行。但是,如果 Not objMyAwesomeObject Is Nothing = False (objMyAwesomeObject Is Nothing = True),则第一个 ElseIf 语句将执行并引发错误,因为您正在尝试访问 Is Nothing 上的 objMyAwesomeObject 属性。 - neizan

1
改进这个答案以解决一个不同问题的基本问题,以下是我选择的做法:
dim conditionsValid as boolean

conditionsValid = Not myObject Is Nothing
if conditionsValid then conditionsValid = myObject.test()
if conditionsValid then conditionsValid = myObject.anotherTest() 

if conditionsValid then
   'do something'
else
   'do something else'
end if

我认为这段代码比其他提出的答案更清晰,而且通常情况下,你不需要为每个验证都使用不同的变量,这是对原问题答案的改进。顺便说一句,每个新的条件只需要增加一行代码。

你的代码中布尔值只是设置错误了。这样才是正确的:conditionsValid = Not myObject Is Nothing。提议的动作“做其他事情”更应该是“进行错误处理”,在你的例子中两个动作听起来是一样的。具体情况可能有所不同。 - user1016274
你是对的 @user1016274。我刚刚修复了它。谢谢 - carlossierra

0
一个解决缺失值的技巧可能会有所帮助:
Dim passed, wrongMaxPercent, wrongPercent, rightMinPercent, rightPercent
wrongPercent = 33
rightPercent = 55

'rightMinPercent = 56
wrongMaxPercent = 40

passed = (Len(wrongMaxPercent) = 0 Or wrongPercent < wrongMaxPercent) And _
         (Len(rightMinPercent) = 0 Or rightPercent >= rightMinPercent)

我相信你的代码是另一个程序的一部分... 你能把它泛化吗?(也就是说,将其制作成适用于任何情况的形式) - Sreenikethan I

0

由于以下语法有效

If myObject.test() Then do something

然后可以使用一行代码来短路评估。下面,第一个If语句确保myObject是某个东西。否则,它甚至不会尝试评估第二个If

If Not myObject Is Nothing Then If myObject.test() Then
    'do something'
Else
    'do something else'
End If

当然,如果您想在 myObject 为空时执行其他操作,那么这种方法可能不适用。


更新于2020/06/30

在一条评论指出这个答案不起作用后,我验证了语法似乎在现代VBA中无法使用。为了遗留目的保留原始答案。


我看到你在这里使用了两个If,一个是用于Not myObject Is Nothing,另一个是用于myObject.test()。但是,由于第一个If语句采用了一行格式,因此第一个If的内容(也就是第二个If不能超过一行 - Sreenikethan I
感谢您提供的建议修改,但是回答中的语法是故意的。我更新了我的答案以更好地反映这一点。 - Cohan
1
这个例子甚至不能编译。怎么有人会点赞这个答案呢? - Olli
因为五年前它确实编译通过了。但是我刚试了一下(已经有一段时间没有再使用VBA了),我同意,它现在的工作方式与以前不同。 - Cohan
2
@Cohan 但如果这能够起作用就太好了 ;) 感谢您的快速回答和更正。VBA的奥秘无穷。 - Olli
显示剩余3条评论

0
If Not myObject Is Nothing Then
    If myObject.test() Then
        'do something'
    End If
Else
   'do something else'
End If

我认为这是你必须这样做的方式。

编辑

也许像这样

Dim bTestsFailed as Boolean
bTestsFailed = False

If Not myObject Is Nothing Then
    If myObject.test() Then
        'do something'
    Else
        bTestsFailed = True
    End If
Else
   bTestsFailed = True
End If

If bTestsFailed Then
    'do something else
End If

VBA不是很棒吗?


1
我认为他希望在myObject为空时也执行else条件,这意味着您必须复制“Else'做其他事情'”。 - BenV
@BenV,@Dick,我擅自编辑了答案,修改成了(我相信是)正确的代码。我同意BenV的观点。 - MarkJ
@MarkJ:我认为他还希望如果myObject不是空值但myObject.test()返回False时,发生其他事情 - BenV
谢谢大家,你们说得对,如果其中一个为假,我需要执行代码。这个方法可以实现,但我认为它有点丑陋;我更喜欢使用“选择语句”解决方案来保持代码的整洁性。 - Luis

0
可以通过使用“或”运算符来切换逻辑条件,并关闭错误消息,方法如下:
Err.Clear
On Error Resume Next
If myObject Is Nothing Or Not myObject.test() Then
    Err.Clear
    'do something else'
Else
    'do something'
End If
On Error Goto 0 ' or On Error Goto ErrorHandler (depending on previous setting in the code)

对于 Nothing 的测试是不必要的 - 它只是为了澄清意思。


0
我们可能想这样说: 如果 XX < 7 那么 XX = 19 但是 XX 可能为空,所以我们必须进行测试。
我们可以这样做: 如果 Switch ( IsNull( XX ) , True, True, XX < 7 ) 那么 XX = 19 因此,如果 XX 为空,我们会继续执行赋值操作,否则只有在测试 XX < 7 为真时才执行。

0

我不知道有没有与OrElse等效的东西,但是对于AndAlso,有一个有限但有用的解决方案。以下两个是等效的:

  • vb.net:If Condition1 AndAlso Condition2 Then DoSomething
  • vba:If Condition1 Then If Condition2 Then DoSomething

它有两个限制:

  1. 它只能作为一行代码使用,不能用于开始if块
  2. 当第一个条件为false时,Else块不会被执行,只有在第一个条件为true且第二个条件为false时才会执行

即使考虑到这两个相当严重的限制,当我没有Else块时,我经常使用这个小技巧。

以下是一个示例:

Sub Test()
  Dim C As Collection

  ' This is what I often use
  If Not C Is Nothing Then If C.Count Then DoSomethingWith C

  ' Here are other usages I stay away from, because of bad readability
  If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
  Set C = New Collection
  If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
  C.Add 1
  If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
End Sub```


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