VBA短路和'替代品

时间:2014-07-08 21:29:30

标签: vba short-circuiting

VBA没有短路

VBA不支持短路 - 显然是因为它只有按位和/或/不等操作。来自VBA language specification:"逻辑运算符是对其操作数执行按位计算的简单数据运算符。"从这个角度来看,VBA设计为true = &H1111false = &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

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

    If cond1 And cond2 Then
        '...
    End If
    

    变为

    If cond1 Then
        If cond2 Then
            '...
        End If
    End If
    

    这样,如果cond2cond1,则If语句会产生类似短路的行为,即无需评估False

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

    If Not MyObj Is Nothing And MyObj.Property = 5 Then
        MsgBox "YAY"
    Else
        MsgBox "BOO"
    End If
    

    变为

    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
    

    有没有办法重写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
    

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

3 个答案:

答案 0 :(得分: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

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

编辑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

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

答案 1 :(得分: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

实施短路条件的短路代码!

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


对于特定用例,多个Set操作对性能影响最小,因此,如果想要避免使用Goto仍然是最全局有效的方法,则代码化+性能方面,避免创建虚拟变量等等 - 尽管对于如此小的代码而言无关紧要,但只需重复命令就可以忽略不计。

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

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

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

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

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

答案 2 :(得分:0)

怎么样:

s = "BOO"

If Not MyObj Is Nothing Then
    If MyObj.Property = 5 Then s = "YAY"
End If

MsgBox s