VBA没有短路
VBA不支持短路 - 显然是因为它只有按位和/或/不等操作。来自VBA language specification:"逻辑运算符是对其操作数执行按位计算的简单数据运算符。"从这个角度来看,VBA设计为true = &H1111
和false = &H0000
是有道理的:这样逻辑语句可以作为按位运算进行评估。
缺少短路会导致问题
性能:评估此语句时将始终运行ReallyExpensiveFunction()
,即使条件左侧的结果不需要
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
错误:如果MyObj为Nothing,则此条件语句将导致运行时错误,因为VBA仍会尝试检查Property
的值
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
我用来实现短效行为的解决方案是嵌套If
s
If cond1 And cond2 Then
'...
End If
变为
If cond1 Then
If cond2 Then
'...
End If
End If
这样,如果cond2
为cond1
,则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...
重复,因此这个问题。
答案 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
(仍然是最全局有效的方法,则代码化+性能方面,避免创建虚拟变量等等 - 尽管对于如此小的代码而言无关紧要,但只需重复命令就可以忽略不计。
只是分析(您的示例代码)可以通过不同的方法获得多少...
就性能而言,跳转通常比比较更昂贵(在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