Bill McCarthy recently asked me a question that’s come up a number of times both internally and externally: why can’t a Catch or Finally block access local variables declared in a Try block? In other words, why doesn’t the following work?
Dim i As Integer = 5
There are four answers to the question: the practical answer, the simple answer, the complex answer and the real answer.
THE PRACTICAL ANSWER: The question is moot because changing it now would potentially break code by changing the binding path. For example:
Private i As Integer
Dim i As Integer
Throw New NullReferenceException()
i = 5
THE SIMPLE ANSWER: Catch and Finally blocks are not children of the Try block, they’re siblings. You can see this by the indentation style and by convention. And the normal scoping rules are that sibling blocks cannot see each other’s locals.
THE COMPLEX ANSWER: You can still say “Yes, but you could still have made an exception in this case, right?” True. However, requiring Catch/Finally variables to be placed outside of the Try block generally results in better code by emphasizing the initial state of the variable (since an exception can occur almost at any time, so the initial state is the only thing you can assume). For example, the following code has a bug — if the File constructor throws an exception, then the Close call is going to throw one too.
Dim F As File = New File("foo.txt")
THE REAL ANSWER: You may find both of these arguments unpersuasive, which is fine — a quick search on Google quickly found similar debates around Java and I’m sure other languages have them too. The real answer is that it’s a judgment call and certainly one that we’ll probably be arguing about from here until eternity…
A simpler version of your complex answer… Although the compiler might reorder statements, the following code doesn’t make logical sense:
Throw New ApplicationException(msg)
Dim s As String = "This statement is never reached"
I guess you could always extend the language like this:
Try With i As Integer = 5
‘ Do something that hurts…
But you’d probably call that syntactic sugar, eh? (But I *like* sweets! 🙂
Actually for the *REAL ANSWER* see:
I personally find it counter-intuitive that variables declared in the try block are not in scope in the catch / finally blocks. The catch / finally blocks are part-and-parcel of the try construct. Can a catch or finally block exist in its own right? Of course not.
Interestingly, the using construct behaves intuitively. A variable declared in a using statement preceding the brackets will be in scope only within the enclosing brackets as you would expect. Knowing what we know about the try construct and scope, you might wonder how this is achieved! It all becomes clear if you look at the C# language specification.
A using construct such as this:
using (Resource r = new Resource())
gets expanded into this:
Resource r = new Resource();
Of particular interest is the outer set of brackets! I rest my case.