VBA短路和'替代品

Bla*_*awk 17 vba short-circuiting

VBA不会短路

VBA不支持短路 - 显然是因为它只有按位和/或/不等操作.从VBA语言规范:"逻辑运算符是对其操作数执行按位计算的简单数据运算符." 从这个角度来看,使用true = &H1111和设计VBA是有道理的false = &H0000:这样,逻辑语句可以被评估为按位运算.

缺少短路可能会导致问题

  1. 性能:在ReallyExpensiveFunction()评估此语句时将始终运行,即使条件左侧的结果不需要

    If IsNecessary() And ReallyExpensiveFunction() Then '... End If

  2. 错误:如果MyObj为Nothing,则此条件语句将导致运行时错误,因为VBA仍将尝试检查值 Property

    If Not MyObj Is Nothing And MyObj.Property = 5 Then '... End If

我用来实现短效行为的解决方案是嵌套If

If cond1 And cond2 Then
    '...
End If
Run Code Online (Sandbox Code Playgroud)

If cond1 Then
    If cond2 Then
        '...
    End If
End If
Run Code Online (Sandbox Code Playgroud)

通过这种方式,If语句给出了类似短路的行为,即不打算评估cond2是否cond1存在False.

如果存在Else子句,则会创建重复的代码块

If Not MyObj Is Nothing And MyObj.Property = 5 Then
    MsgBox "YAY"
Else
    MsgBox "BOO"
End If
Run Code Online (Sandbox Code Playgroud)

If Not MyObj Is Nothing Then
    If MyObj.Property = 5 Then
        MsgBox "YAY"
    Else
        MsgBox "BOO" 'Duplicate
    End If
Else
    MsgBox "BOO" 'Duplicate
End If
Run Code Online (Sandbox Code Playgroud)

有没有办法重写If语句以保持短路行为,但避免重复代码?

也许还有另一个分支声明Select Case吗?


要为问题添加上下文,这是我正在查看的具体案例.我正在实现一个哈希表,通过在链表中链接它们来处理冲突.底层数组大小强制为2的幂,并且通过将散列截断为适当的长度将散列分配到当前数组大小.

例如,假设数组长度为16(二进制10000).如果我有一个哈希值为27(二进制11011)的密钥,我可以将它存储在我的16个槽位数组中,只保留在该数组大小限制内的位.其中此文件将被存储的索引是(hash value) And (length of array - 1)在这种情况下是(binary 11011) And (1111)其为1011这是11的实际的哈希码与在狭槽的键一起存储.

在链中的哈希表中查找项时,必须检查哈希和键,以确定找到了正确的项.但是,如果哈希不匹配,则没有理由检查密钥.我希望通过嵌套Ifs以获得短路行为来获得一些微小的无形性能:

While Not e Is Nothing
    If keyhash = e.hash Then
        If Key = e.Key Then
            e.Value = Value
            Exit Property
        Else
            Set e = e.nextEntry
        End If
    Else
        Set e = e.nextEntry
    End If
Wend
Run Code Online (Sandbox Code Playgroud)

你可以看到Set...重复,因此这个问题.

pet*_*oak 11

作为一个更一般的评论,我建议引入条件标志并使用将分配结果分配给布尔值:

dim cond1 as boolean
dim cond2 as boolean

cond1 = false
cond2 = false

' Step 1
cond1 = MyObj Is Nothing

' Step 2: do it only if step 1 was sucessful 
if cond1 then
    cond2 = MyObj.Property = 5
end if

' Final result:
if cond2 then
   msgbox "Yay"
else
   msgbox "Boo"
end if
Run Code Online (Sandbox Code Playgroud)

通过"链接"这些条件标志,每一步都是安全的,您在最后一个条件标志中看到最终结果,并且您不进行不必要的比较.而且,对我而言,它始终可读.

编辑14/09/07

我通常永远不会省略块分隔符,因此我将控制结构的每个语句都设置在一个新行上.但在这种情况下,您可以仔细获取一个非常密集的符号,提醒短路符号,也因为VBA编译器启动变量:

dim cond1 as boolean
dim cond2 as boolean
dim cond3 as boolean
dim cond4 as boolean

cond1 = MyObj Is Nothing
if cond1 then cond2 = MyObj.Property = 5
if cond2 then cond3 = MyObj.Property2 = constSomething
if cond3 then cond4 = not isNull(MyObj.Property77)

if cond4 then
   msgbox "Hyper-Yay"
else
   msgbox "Boo"
end if
Run Code Online (Sandbox Code Playgroud)

我同意这一点.这是一个清晰的阅读流程.

  • 谢谢.我同意有更多的方法,这取决于具体情况.嗯......还有一个通用的`函数IfShortcut(ParamArray conditions())作为boolean ...`会很好......在这种情况下,你可以循环和`退出`. (2认同)

hnk*_*hnk 5

有一种方法.你无法保证喜欢它.但这是精心构建的案例之一,Goto派上用场

If Not MyObj Is Nothing Then
    If MyObj.Property = 5 Then
        MsgBox "YAY"
    Else
        Goto JUMPHERE
    End If
Else
JUMPHERE:
    MsgBox "BOO" 'Duplicate
End If
Run Code Online (Sandbox Code Playgroud)

用于实现短路状态的短路代码!

或者,如果不是MsgBox "BOO"一些冗长而复杂的代码,它可以包装在一个函数中,并且可以以最小的影响/开销写入两次.


关于特定用例,多个Set操作将对性能影响最小,因此,如果想要避免使用Goto(仍然是最全局有效的方法,代码化+性能明智,避免创建虚拟变量等 - 将无关紧要但是,对于这么一小段代码而言,简单地重复命令就有微不足道的缺点.

只是为了分析(你的示例代码)可以通过不同的方法获得多少...

  • 如果两个条件都为真:,则有2个比较,1个赋值,0个跳转
  • 如果只有第一个条件为真:有2个比较,1个指针赋值,1个跳转
  • 如果只有第二个条件为真:有1个比较,1个指针赋值,1个跳转
  • 如果两个条件都为假: 有1个比较,1个指针赋值,1个跳转(与上面相同)

在性能方面,跳转通常比比较更昂贵(在ALU中发生的速度非常快,而跳转可能会导致代码缓存中断,可能不是这些尺寸,但跳转仍然很昂贵).

按值进行的正常赋值最多与指针赋值一样快或有时更差(这是VBA,不能100%确定p代码实现)

因此,根据您的用例/预期数据,您可以尝试最小化循环中每次迭代的平均跳转次数并重新排序代码.

  • +1"你不能保证喜欢它":P这绝对可以解决它,但是,如果我不小心,那么'Goto`会给我足够的空间来修改我的代码.跳出分支或循环语句是否会引起您认为的问题? (2认同)
  • 不,内部C ++ / VBA的p代码等始终将分支转换为Goto,因此没有实现问题。只有哲学上的。但是,针对Goto的争论(主要是Djikstra的争论)是从避免意大利面条式代码的角度出发的。我进行任何优化的方法是,首先编写简洁的代码。然后将其全部注释掉,并在其旁边放置超优化的一个。这样,请阅读常规代码以进行逻辑和调试,并对其进行超优化和彻底审查以获取真实性能。尤其是在这种情况下,Goto使其更易于阅读-消除代码混乱! (2认同)
  • 对于纯粹主义者:您可以使用布尔标志而不是GOTO来获得相同的结果. (2认同)