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?
Try
Dim i As Integer = 5
Finally
Console.WriteLine(i)
End Try
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:
Class Foo
Private i As Integer
Sub Bar()
Try
Dim i As Integer
Throw New NullReferenceException()
Catch
i = 5
End Try
End Sub
End Class
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.
Try
Dim F As File = New File("foo.txt")
F.Read(...)
Finally
F.Close()
End Try
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:
<PRE>Try
Throw New ApplicationException(msg)
…
Dim s As String = "This statement is never reached"
…
Finally
Trace.Write(s)
End Try</PRE>
I guess you could always extend the language like this:
Try With i As Integer = 5
‘ Do something that hurts…
Finally
Console.WriteLine(i)
End Try
But you’d probably call that syntactic sugar, eh? (But I *like* sweets! 🙂
Pingback: Anonymous
Actually for the *REAL ANSWER* see:
http://msmvps.com/bill/posts/7433.aspx
Pingback: Anonymous
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())
{
r.DoSomething();
}
gets expanded into this:
{
Resource r = new Resource();
try
{
r.DoSomething();
}
finally
{
((IDisposable)r).Dispose();
}
}
Of particular interest is the outer set of brackets! I rest my case.