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认同)