This question already has answers here:
AndAlso/OrElse in VBA
(10 answers)
Closed 8 years ago.
VBA doesn't short-circuit
VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations. From the VBA language specification: "Logical operators are simple data operators that perform bitwise computations on their operands." In this light, it makes sense that VBA was designed with true = &H1111 and false = &H0000: this way logical statements can be evaluated as bitwise operations.
The lack of short-circuiting can cause problems
Performance: the ReallyExpensiveFunction() will always be run when this statement is evaluated, even if it is not necessary by the result of the left hand side of the condition
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
Errors: if MyObj is Nothing, this conditional statment will result in a runtime error because VBA will still try to check the value of Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
The solution I've used to implement short-cirtcuiting behavior is nested Ifs
If cond1 And cond2 Then
'...
End If
Becomes
If cond1 Then
If cond2 Then
'...
End If
End If
This way the If statements give the short-circuit-like behavior of not bothering to evaluate cond2 if cond1 is False.
If there is an Else clause, this creates duplicate code blocks
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
Becomes
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
Is there a way to rewrite If statements to preserve the short-circuit behavior, but avoid duplication of code?
Perhaps with another branching statement like Select Case?
To add context to the question, here is the specific case I'm looking at. I'm implementing a hash table that handles collisions by chaining them in a linked list. The underlying array size is enforced to be a power of two and the hashes are distributed into the current array size by truncating them to the appropriate length.
For example, suppose the array length is 16 (binary 10000). If I have a key that hashes to 27 (binary 11011), I can store it in my 16 slot array by keeping only the bits within the limit of that array size. The index where this item would be stored is (hash value) And (length of array - 1) which in this case is (binary 11011) And (1111) which is 1011 which is 11. The actual hash code is stored along with the key in the slot.
When looking up an item in the hash table in a chain, both the hash and the key must be checked to determine that the correct item has been found. However, if the hash doesn't match, then there is no reason to check the key. I was hoping to gain some tiny intangible amount of performance by nesting the Ifs to get the short-circuit behavior:
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
You can see the Set... is duplicated, and thus this question.
As a more general apprach, I suggest to introduce condition flags and make usage of assigning comparison results to booleans:
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
By "chaining" those condition flags, every step is safe, you see the final result in the last condition flag and you don't do unnecessary comparisons. And, to me, it keeps readable.
EDIT 2014-07-09
I usually never omit block delimiters and I consequently set every statement of control structures on a new line. But in this case, you can carefully get a very dense notation that reminds on short-circuit notation, also because the VBA compiler initiates the variables:
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
I could agree to this. It's a clear flow to read.
EDIT 2021-03-21
Thanks to #Tom's comment, one can write it simpler:
dim cond as boolean
cond = MyObj Is Nothing
if cond then cond = MyObj.Property = 5
if cond then cond = MyObj.Property2 = constSomething
if cond then cond = not isNull(MyObj.Property77)
if cond then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
#Tom explains the advantages in his comment below. I fully agree with this. I can only imagine some situations while debugging, when I would like to have separated results of the conditions, and therefore explicitely with four different variables.
There is a way. You're not guaranteed to like it. But this is one of those carefully constructed cases where Goto comes in handy
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
A short-circuited code to implement a short-circuited condition!
Alternately, if instead of MsgBox "BOO" is some long and convoluted code, it can be wrapped in a function and that can be written twice with minimal impact/overhead.
Regarding the specific use case, the multiple Set operations will have a minimal performance impact and hence, if one wants to avoid using Goto (still the most globally efficient approach, codesize + performance wise, avoiding creation of dummy variables, etc. - won't matter, though for such a small piece of code) there is negligible downside in simply repeating the command.
Just to analyze (your sample code) how much can be gained by different methods...
If both conditions are true:, there are 2 comparisons, 1 assignment, 0 jumps
If only first condition is true: there are 2 comparisons, 1 pointer-assignment, 1 jump
If only second condition is true: there is 1 comparison, 1 pointer-assignment, 1 jump
If both conditions are false: there is 1 comparison, 1 pointer-assignment, 1 jump (same as above)
In terms of performance, a jump is usually more expensive than comparison (which happens very quickly in ALU vs. the jump which could lead to a disruption in the code cache, maybe not at these sizes, but still jumps are expensive).
And normal assignment by value would be at best as fast as a pointer-assignment or sometimes worse (this is VBA, can't be 100% sure of the p-code implementation)
So, depending on your use case / expected data, you can try to minimize average number of jumps per iteration in your loop and reorder the code.
How about:
s = "BOO"
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then s = "YAY"
End If
MsgBox s
Related
The title really says it all. In VB.NET is there a difference between these statements when checking for value equality
Dim notEqualsCompare as Boolean = Not a = b
Dim angleBracCompare as Boolean = a <> b
I usually use 'Not a = b' just because it reads better.
I read the '<>' operator as 'is less or greater than' which only really makes sense for numeric values, but from my brief testing they behave the same.
Is there ever a situation where one is preferable?
In practice, they should always be functionally equivalent.
As mentioned in one of the other answers, Not a = b is logically two separate operations. You may trust that the compiler will optimize that for you, but, regardless, most programmers would agree that, if a language has an operator to do what you want, you should generally use that feature of the language rather than using multiple operations to accomplish the same result. If you don't like the language, choose another language. But if you're going to use VB, it's best to get used to it and take advantage of it's syntax.
Technically, though, it's worth mentioning that they aren't necessarily the same. All three of the following lines could technically have different results:
result = Not a = b
result = a <> b
result = Not a.Equals(b)
The reason why they can be different is because VB allows you to override the = operator (equality test, not assignment), the <> operator, the Not operator, and the Equals method. So, even though it would be horrendous to override those things and provide differing functionality for each, it is technically possible. For obvious reasons, the "official" guidelines from Microsoft recommend that they always work consistent to each other.
Consider:
Public Sub Main()
Dim a As New Crazy()
Dim b As New Crazy()
Console.WriteLine(a = b) ' False
Console.WriteLine(a.Equals(b)) ' True
Console.WriteLine(a <> b) ' False
Console.WriteLine(Not a) ' "Opposite"
Console.WriteLine(Not a = b) ' True
Console.WriteLine(Not a.Equals(b)) ' False
End Sub
Private Class Crazy
Public Shared Operator <>(x As Crazy, y As Crazy) As Boolean
Return False
End Operator
Public Shared Operator =(x As Crazy, y As Crazy) As Boolean
Return False
End Operator
Public Overrides Function Equals(obj As Object) As Boolean
Return True
End Function
Public Shared Operator Not(value As Crazy) As String
Return "Opposite"
End Operator
End Class
They both produce the same answer, however, a <> b would probably be more efficient. The reason being is that when the compiler comes to "Not a = b", the compiler must first do the comparison (a = b), then it must use clock cycles to flip that login (Not). When doing a <> b, the compiler would only have to do the comparison and then it's done. Look at this chart:
a <> b ' One Operation
c = a = b ' One Operation
Not c ' Another Operation
I have a VBA code with more then 50 lines of If Then ElseIf function. When I run macro, it will satisfy one "if" condition. If the data meet, 50th Elseif condition, then macro will go through unto 49 if condition. I know, one basic value that meet the condition. Since I am able to find value that meet condition, Can I directly jump into that particular if Condition? Any help would be much appreciated
If A=10 and B=39 then
Do Something....
Elseif A=11 and B=30 and C=56 then
Do Something....
Elseif A=13 and B=35 and C=60 then
Do Something....
...
etc
Here Since I know A=13, Can I directly jump to
Elseif A=13 and B=35 and C=60 then
without going through first two if condition?
To your question - can you prioritize the 49. ElseIf condition - NO.
But you can put it in number 1 or 2, if you want to do it faster in debugging.
Like this:
Public Sub TestMe()
Dim a As Long
a = 5
If a < 6 Then
Debug.Print "The most popular"
ElseIf a = 5 Then
Debug.Print "The 2. most popular"
ElseIf a > 2 Then
Debug.Print "The 3. most popular"
End If
End Sub
As mentioned in the comments, the fact that you would have about 50 small evaluations would not slow your code (unless there is a non optimized function in the evaluations or you are accessing a database there).
Hello I'm just wondering whether an 'if' statement can be considered as an Iteration. Because Iteration is used until a certain criteria is meet to allow the code to continue.
An iteration is one cycle (one time through) a loop. If blocks are not loops.
For blocks or While blocks are loops, and executing one cycle of the contents of one of those blocks — what's inside the For block or the While block — is one iteration of the loop. If the entirety of a For block or While block is a conditional If block, then the If block could be one iteration... but not because it's an If block, but because it's what's inside the loop.
No, an iteration is a repetition of some code. Once doesn't count as a repetition, else all code would be considered an iteration.
The If statement by itself cannot be considered iteration. You can run a code block containing an if statement as many times as you wish, but this doesn't make the if statement an iterator by itself. It's what calls that code block which could stand for iteration.
If statement has some similarities with those other conditional statements :
Select Case
Try Catch
Explicit iterators are the followings :
For/Next - For Each/Next
Do/Loop
While/End While
However, you have some logic that could turn the usage of an if statement as the trigger to resume or stop conditions of a loop, without the usage of direct loop statements like For or While. But that doesn't turn that if statement an iterator by itself, because the iteration is the combination of the if statement and a specific logic allowed by the programming language.
Recursion :
Private Function ResumeIncrement(ByRef Number As Int32) As Boolean
Number = Number + 1
If Number < 10 Then
Return ResumeIncrement(Number)
Else
Return False
End If
End Function
GoTo statement :
Private Sub TestGoTo()
Dim Number As Int32 = 0
IncrementMore:
Number = Number + 1
If Number < 10 Then
GoTo IncrementMore
End If
End Sub
And as stated by Joel Coehoorn above, the If block can contain anything, including iterator blocks triggered by For, While or Do. If you remove the contained block, that doesn't change anything in what the if part is supposed to do : a conditional check !
You can also do the inverse, and use an If block to control the way an iteration behaves. Like :
If SomeCondition Then
Exit For
' Or Exit While, etc. ie, using the 'Exit' statement
End If
or
i = 0
Do
i = i + 1
' ^^ capture i in Debug or Console
' Control the value of i...
If i Mod 2 = 0 Then
i = i - 2
Else
i = i * 3
End If
Loop While i < 30
' i = 1, 4, 3, 10, 9, 28, 27, [Exit Do with i = 81]
In the two examples above, the If statement is either there to break the iteration, or try again, that said, to "control the iteration", but in no means, to stand for iterator by itself.
Here is also another answer to clear out one uncertainty. You asked
Are if statements considered as an Iteration ?
(You made "if statements" plural) Then you said
Hello I'm just wondering whether an 'if' statement can be considered
as an Iteration. Because Iteration is used until a certain
criteria is meet to allow the code to continue.
(then you made it singular.. ???) That "...Iteration is used until..." should self answer your question alone, assuming you're iterating code blocks (which is the way it is understood here)
But what if you're talking about iterating some possible values until one meets your requirements..?
If MyValue = "" Then
' ...
ElseIf MyValue.StartsWith("A") Then
' ...
ElseIf MyValue.ToUpper = "TEST" Then
' ...
Else
' ...
End If
is similar to :
Select Case True
Case MyValue = "":
' ...
Case MyValue.StartsWith("A")
' blah blah blah
End Select
or
If MyValue = "" Then GoTo EmptyString
If MyValue.StartsWith("A") Then GoTo StartsWithA
If MyValue.ToUpper() = "TEST" Then GoTo IsTEST Else GoTo DoNothing
EmptyString:
' ...
GoTo DoNothing
StartsWithA:
' ...
GoTo DoNothing
IsTEST:
' ...
GoTo DoNothing
DoNothing:
' Resume...
Etc.
That's not an iteration ! That's a list of evaluation that are processed in order. When a condition evaluates to True, the related code is executed. Unlike iteration, this is just a matter of selecting the right path and resume, not walking the very same path several times (loops)
Yes, you're iterating through a collection of possible values (condition states) but NO, you're not iterating the If statement. You're only checking the result of each single If test once.
This question already has answers here:
AndAlso/OrElse in VBA
(10 answers)
Closed 8 years ago.
VBA doesn't short-circuit
VBA does not support short-circuiting - apparently because it only has bitwise And/Or/Not etc operations. From the VBA language specification: "Logical operators are simple data operators that perform bitwise computations on their operands." In this light, it makes sense that VBA was designed with true = &H1111 and false = &H0000: this way logical statements can be evaluated as bitwise operations.
The lack of short-circuiting can cause problems
Performance: the ReallyExpensiveFunction() will always be run when this statement is evaluated, even if it is not necessary by the result of the left hand side of the condition
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
Errors: if MyObj is Nothing, this conditional statment will result in a runtime error because VBA will still try to check the value of Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
The solution I've used to implement short-cirtcuiting behavior is nested Ifs
If cond1 And cond2 Then
'...
End If
Becomes
If cond1 Then
If cond2 Then
'...
End If
End If
This way the If statements give the short-circuit-like behavior of not bothering to evaluate cond2 if cond1 is False.
If there is an Else clause, this creates duplicate code blocks
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
Becomes
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
Is there a way to rewrite If statements to preserve the short-circuit behavior, but avoid duplication of code?
Perhaps with another branching statement like Select Case?
To add context to the question, here is the specific case I'm looking at. I'm implementing a hash table that handles collisions by chaining them in a linked list. The underlying array size is enforced to be a power of two and the hashes are distributed into the current array size by truncating them to the appropriate length.
For example, suppose the array length is 16 (binary 10000). If I have a key that hashes to 27 (binary 11011), I can store it in my 16 slot array by keeping only the bits within the limit of that array size. The index where this item would be stored is (hash value) And (length of array - 1) which in this case is (binary 11011) And (1111) which is 1011 which is 11. The actual hash code is stored along with the key in the slot.
When looking up an item in the hash table in a chain, both the hash and the key must be checked to determine that the correct item has been found. However, if the hash doesn't match, then there is no reason to check the key. I was hoping to gain some tiny intangible amount of performance by nesting the Ifs to get the short-circuit behavior:
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
You can see the Set... is duplicated, and thus this question.
As a more general apprach, I suggest to introduce condition flags and make usage of assigning comparison results to booleans:
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
By "chaining" those condition flags, every step is safe, you see the final result in the last condition flag and you don't do unnecessary comparisons. And, to me, it keeps readable.
EDIT 2014-07-09
I usually never omit block delimiters and I consequently set every statement of control structures on a new line. But in this case, you can carefully get a very dense notation that reminds on short-circuit notation, also because the VBA compiler initiates the variables:
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
I could agree to this. It's a clear flow to read.
EDIT 2021-03-21
Thanks to #Tom's comment, one can write it simpler:
dim cond as boolean
cond = MyObj Is Nothing
if cond then cond = MyObj.Property = 5
if cond then cond = MyObj.Property2 = constSomething
if cond then cond = not isNull(MyObj.Property77)
if cond then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
#Tom explains the advantages in his comment below. I fully agree with this. I can only imagine some situations while debugging, when I would like to have separated results of the conditions, and therefore explicitely with four different variables.
There is a way. You're not guaranteed to like it. But this is one of those carefully constructed cases where Goto comes in handy
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
A short-circuited code to implement a short-circuited condition!
Alternately, if instead of MsgBox "BOO" is some long and convoluted code, it can be wrapped in a function and that can be written twice with minimal impact/overhead.
Regarding the specific use case, the multiple Set operations will have a minimal performance impact and hence, if one wants to avoid using Goto (still the most globally efficient approach, codesize + performance wise, avoiding creation of dummy variables, etc. - won't matter, though for such a small piece of code) there is negligible downside in simply repeating the command.
Just to analyze (your sample code) how much can be gained by different methods...
If both conditions are true:, there are 2 comparisons, 1 assignment, 0 jumps
If only first condition is true: there are 2 comparisons, 1 pointer-assignment, 1 jump
If only second condition is true: there is 1 comparison, 1 pointer-assignment, 1 jump
If both conditions are false: there is 1 comparison, 1 pointer-assignment, 1 jump (same as above)
In terms of performance, a jump is usually more expensive than comparison (which happens very quickly in ALU vs. the jump which could lead to a disruption in the code cache, maybe not at these sizes, but still jumps are expensive).
And normal assignment by value would be at best as fast as a pointer-assignment or sometimes worse (this is VBA, can't be 100% sure of the p-code implementation)
So, depending on your use case / expected data, you can try to minimize average number of jumps per iteration in your loop and reorder the code.
How about:
s = "BOO"
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then s = "YAY"
End If
MsgBox s
What is the difference between (OrElse and Or) and (AndAlso and And)?
Is there any difference in their performances, let say the correctness benefit?? Is there any situation that I shoudn't use OrElse and AndAlso?
Or/And will always evaluate both1 the expressions and then return a result. They are not short-circuiting.
OrElse/AndAlso are short-circuiting. The right expression is only evaluated if the outcome cannot be determined from the evaluation of the left expression alone. (That means: OrElse will only evaluate the right expression if the left expression is false, and AndAlso will only evaluate the right expression if the left expression is true.)
Assuming that no side effects occur in the expressions and the expressions are not dependent (and any execution overhead is ignored), then they are the same.
However, in many cases it is that the expressions are dependent. For instance, we want to do something when a List is not-Nothing and has more than one element:
If list IsNot Nothing AndAlso list.Length > 0 Then .. 'list has stuff
This can also be used to avoid an "expensive" computation (or side-effects, ick!):
If Not Validate(x) OrElse Not ExpensiveValidate(x) Then .. 'not valid
Personally, I find that AndAlso and OrElse are the correct operators to use in all but the 1% - or less, hopefully! - of the cases where a side-effect is desired.
Happy coding.
1 An Exception thrown in the first expression will prevent the second expression from being evaluated, but this should hardly be surprising ..
Besides the short-circuiting mentioned in the other answers, Or/And are usable as bitwise operators where OrElse/AndAlso are not. Bitwise operations include combining values of Flags enums, such as the FileAttributes enumeration where you might indicate a file is both read only and hidden by FileAttributes.ReadOnly Or FileAttributes.Hidden
The difference is that OrElse and AndAlso will short-circuit based on the first condition, meaning that if the first condition doesn't pass, the second (or more) conditions will not be evaluated. This is particularly useful when one of the conditions might be more intensive than the other.
Example where Or is fine (both conditions evaluated):
If Name = "Fred" Or Name = "Sam" Then
It really doesn't matter which way around they are evaluated
The following AndAlso is useful because the second condition might fail
If Not SomeObject Is Nothing AndAlso CheckObjectExistsInDatabase(SomeObject) Then
This allows for the first condition to check whether the object has been set and only if it has been set will go and check the database (or some other task). If this had been a plain And keyword, both would be evaluated.
#Gideon - glad someone pointed that out. Here is a simple test that shows the dramatic impact of AndAlso:
Dim tm As New Stopwatch
Const tries As Integer = 123456
Dim z As Integer = 0
Dim s() As String = New String() {"0", "one"}
Debug.WriteLine("AndAlso")
For x As Integer = 0 To s.Length - 1
z = 0
tm.Restart() 'restart the stopwatch
For y As Integer = 0 To tries
If s(x) = x.ToString AndAlso s(x) = y.ToString Then '<<<<<<<<<<
z += 1
End If
Next
tm.Stop()
Debug.WriteLine(x.ToString.PadRight(3, " "c) & z.ToString.PadRight(10, " "c) & tm.Elapsed.ToString)
Next
Debug.WriteLine("And")
For x As Integer = 0 To s.Length - 1
z = 0
tm.Restart() 'restart the stopwatch
For y As Integer = 0 To tries
If s(x) = x.ToString And s(x) = y.ToString Then '<<<<<<<<<<
z += 1
End If
Next
tm.Stop()
Debug.WriteLine(x.ToString.PadRight(3, " "c) & z.ToString.PadRight(10, " "c) & tm.Elapsed.ToString)
Next