I thought I had understood how exception handling in C# works. Re-reading the documentation for fun and self-confidence, I have run into problems:
This document claims that the following two code snippets are equivalent, even more, that the first one is translated to the latter one at compile time.
using (Font font1 = new Font("Arial", 10.0f)) {
byte charset = font1.GdiCharSet;
}
and
{
Font font1 = new Font("Arial", 10.0f);
try {
byte charset = font1.GdiCharSet;
}
finally {
if (font1 != null)
((IDisposable)font1).Dispose();
}
}
Furthermore, it claims:
The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object.
In contrast, that document states:
Within a handled exception, the associated
finally
block is guaranteed to be run. However, if the exception is unhandled, execution of thefinally
block is dependent on how the exception unwind operation is triggered.
I do not get this together. In the code example from the first document, the exception clearly is unhandled (since there is no catch block). Now, if the statement from the second document is true, the finally
block is not guaranteed to execute. This ultimately contradicts what the first document says ("The using
statement ensures ...") (emphasis mine).
So what is the truth?
EDIT 1
I still don't get it. StevieB's answer has made me read more parts from the C# language specification. In section 16.3, we have:
[...] This search continues until a catch clause is found that can handle the current exception [...] Once a matching catch clause is found, the system prepares to transfer control to the first statement of the catch clause. Before execution of the catch clause begins, the system first executes, in order, any finally clauses that were associated with try statements more nested that than the one that caught the exception.
So have I made a simple test program which contains code which produces a division by zero and is within a try
block. That exception is never caught in any of my code, but the respective try
statement has a finally
block:
int b = 0;
try {
int a = 10 / b;
}
finally {
MessageBox.Show("Hello");
}
Initially, according to the documentation snippet above, I had expected that the finally
block never would be executed and that the program just would die when being executed without a debugger attached. But this is not the case; instead, the "exception dialog box" we all know too well is shown, and after that, the "Hello" dialog box appears.
After thinking a while about that and after having read docs, articles and questions like this and that, it became clear that this "exception dialog box" is produced by a standard exception handler which is built into Application.Run() and the other usual methods which could "start" your program, so I am not wondering any more why the finally
block is run.
But I am still totally baffled because the "Hello" dialog appears after the "exception dialog box". The documentation snippet above is pretty clear (well, probably I am just too silly again):
The CLR won't find a catch
clause which is associated with the try
statement where the division by zero happens. So it should pass the exception up one level to the caller, won't find a matching catch
clause there as well (there is not even a try
statement there) and so on (as noted above, I do not handle (i.e. catch) any exception in this test program).
Finally, the exception should meet the CLR's default catch-all exception handler (i.e. that one which is by default active in Application.Run() and its friends), but (according to the documentation above) the CLR should now execute all finally
blocks which are more deeply nested than that default handler ("my" finally
block belongs to these, doesn't it?) before executing the CLR catch-all default handler's catch
block.
That means that the "Hello" dialog should appear before the "exception dialog box", doesn't it?. Well, obviously, it's the other way around. Could somebody elaborate on that?
Yes, it absolutely will. Assuming your finally block doesn't throw an exception, of course, in which case that will effectively "replace" the one that was originally thrown.
C# exception handling is built upon four keywords: try, catch, finally, and throw.
By using a finally block, you can clean up any resources that are allocated in a try block, and you can run code even if an exception occurs in the try block. Typically, the statements of a finally block run when control leaves a try statement.
This document claims that the following two code snippets are equivalent
They are.
The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object.
Pretty much.
This ultimately contradicts what the first document says
Well, the first one was being a bit too vague, rather than flat-out incorrect.
There are cases where a finally
will not run, including that implied by a using
. A StackOverflowException
would be one example (a real one from overflowing the stack, if you just do throw new StackOverflowException()
the finally will run).
All the examples are things you can't catch, and your application is going down, so if the clean up from using
is only important while the application is running, then finally
is fine.
If the clean-up is vital even when the program crashes, then finally
can never be enough, as it can't deal with e.g. a power plug being pulled out, which in the sort of cases where clean up is vital even in a crash, is a case that needs to be considered.
In any case where the exception is caught further up and the program continues, the finally
will run.
With catchable exceptions that aren't caught, then finally
blocks will generally run, but there are still some exceptions. One would be if the try
-finally
was inside a finaliser and the try
took a long time; after a while on the finaliser queue the application will just fail-fast.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With