Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception handling (contradicting documentation / try-finally vs. using)

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 the finally 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?

like image 210
Binarus Avatar asked Feb 15 '17 13:02

Binarus


People also ask

Does try finally Catch exceptions?

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.

What are the four keywords for exception handling?

C# exception handling is built upon four keywords: try, catch, finally, and throw.

Why use finally block in C#?

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.


1 Answers

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.

like image 65
Jon Hanna Avatar answered Oct 10 '22 00:10

Jon Hanna