Block-Level Scope in VBA...Does Not Exist
VB.NET and VBA may look the same, but there are some important differences. This is a little-known feature of VB.NET that I really wish was available in VBA.
One of many differences between VBA and VB.NET is that VB.NET has block-level scope and VBA does not.
A "block" is a set of statements with an opening and closing line of code, such as:
If
...End If
With
...End With
Select Case
...End Select
Do
...Loop
With block-level scope, "if you declare a variable within a block, you can use it only within that block."
Block-Level Scope Restrictions in VB.NET
If you try to run the code below in VB.NET, you will get a bunch of compile errors as indicated in the comments and shown in the screenshot below the code:
Sub TestDim()
Console.WriteLine(a) '<-- Local variable 'a' cannot be referred to before it is declared.
Dim a As Long
If True Then
Dim b As Long
End If
Console.WriteLine("b:" & b) '<-- 'b' is not declared. It may be inaccessible due to its protection level.
If False Then
Dim c As Long
End If
Console.WriteLine("c:" & c) '<-- 'c' is not declared. It may be inaccessible due to its protection level.
With Today
Dim d As Long
End With
Console.WriteLine("d:" & d) '<-- 'd' is not declared. It may be inaccessible due to its protection level.
Select Case a
Case 0
Dim e As Long
Case 1
Dim f As Long
Case Else
Dim g As Long
End Select
Console.WriteLine("e:" & e) '<-- 'e' is not declared. It may be inaccessible due to its protection level.
Console.WriteLine("f:" & f) '<-- 'f' is not declared. It may be inaccessible due to its protection level.
Console.WriteLine("g:" & g) '<-- 'g' is not declared. It may be inaccessible due to its protection level.
For a = 1 To 2
Dim h As Long
Next
Console.WriteLine("h:" & h) '<-- 'h' is not declared. It may be inaccessible due to its protection level.
Do Until a > 2
Dim i As Long
a = a + 1
Loop
Console.WriteLine("i:" & i) '<-- 'i' is not declared. It may be inaccessible due to its protection level.
End Sub
No Block-Level Scope Restrictions in VBA
The nearly identical code below WILL compile in VBA.
(I commented out the first line because that is the only one that will throw a compile error in VBA.)
Sub TestDim()
'Debug.Print a '<-- Compile error: Variable not defined (if uncommented)
Dim a As Long
If True Then
Dim b As Long
End If
Debug.Print "b:" & b 'Available even though declared inside If ... End If block
If False Then
Dim c As Long
End If
Debug.Print "c:" & c 'Available even though its declaration can't be reached at runtime
With CurrentProject
Dim d As Long
End With
Debug.Print "d:" & d 'Available even though declared inside With ... End With block
Select Case a
Case 0
Dim e As Long
Case 1
Dim f As Long
Case Else
Dim g As Long
End Select
'All three variables are available even though declared inside Select Case block
Debug.Print "e:" & e
Debug.Print "f:" & f
Debug.Print "g:" & g
For a = 1 To 2
Dim h As Long
Next
Debug.Print "h:" & h 'Available even though declared inside For ... Next block
Do Until a > 2
Dim i As Long
a = a + 1
Loop
Debug.Print "i:" & i 'Available even though declared inside Do Until ... Loop block
End Sub
Only One VBA Rule - Declare Before Use
Assuming you are using Option Explicit
(and you should always use Option Explicit), you only need to remember one rule when it comes to local variables:
In VBA, you must declare a local variable before you use it.
That's it. The block-level scoping restrictions in VB.NET do not apply to VBA. In VBA, the most restrictive level of scope is procedure-level scope.
This is a VBA Shortcoming
Why? Because it eliminates an entire class of potential compile errors. And compile errors are good! Okay, maybe they're not good, per se. But they are much cheaper and easier to fix than logic errors.
So, even though the VBA compiler won't enforce block-level scope, I still try to write my code as if it did. In other words, if I only need to use a variable inside of a code block, then I will:
- Declare the variable inside the code block
- Not reference the variable outside the code block
While I would much rather the compiler enforce this restriction, adopting this as a policy still allows me to convey my intent for how I expect the variable to be used within the procedure.