Friday, July 18, 2008

Why Try? Finally!

Today we talk about the "try/finally" construct. This article should be very simple for most of you with any C# or C++ experience to understand. I've written a sample that you will find at the end of this article to let you see the various affects of the "finally" statement under different scenarios.

But why use "finally" at all? I like to think of "finally" as being to the flow of logic what the "destructor" or "dispose" is to the life of an object. It is a common place to put all the necessary logic for shutting down the logic context. With the ubiquitous "execption" lurking behind every corner, you never know when you will be forced to leave the context, and it's nice to have a place for common cleanup code. You say you could just write the cleanup in the common exception handler. But then you have to repeat the code, line for line, in every other named exception handler as well as the error free path of execution (not to mention, you may just want the exception to go unhandled). The "finally" construct allows you to avoid duplication of logic or insertion of unnecessary logic. One of my favorite examples of the try/finally construct is the use of "Monitor".


Monitor.Enter(lockObject);
try {
// critical section
}
finally {
Monitor.Exit(lockObject);
}


Here, if the Monitor.Enter() is successful, it MUST be followed by a Monitor.Exit() or else everthing could come to a halt. This example has been replaced by the built-in compiler directive "lock(lockObject){...}" but that isn't always the best choice.

Let me repeat. The main usefulness of "finally" is to avoid duplication of cleanup logic whether normal or exceptional. It provides one place to write the logic (and maintain it).

Here is a break-down of the how the this features behaves. First, we start with one of the simplest examples of try/finally.


try {
// do your thing
Console.WriteLine("Example 1: In Try.");
}
finally {
// run some code that must alway follow our "try"
Console.WriteLine("Example 1: In Finally");
}
// stuff to run after the try/finally
Console.WriteLine("Example 1: After the Try/Finally");


The compiler guarantees that the "finally" block of code will run upon the exit of the "try" block, regardless of how the block exist. In Example 1, the try block exits according to the normal flow of code falling out of the block. Example 2 demonstrates some more normal flow, but this time it is through the "return" statement.


try
{
Console.WriteLine("Example 2: Try called");
return;
}
finally
{
Console.WriteLine("Example 2: Finally called");
}
Console.WriteLine("Example 2: More code called");


Here you can see that even though we "return" within the "try", the "finally" is still run. But, the stuff following the "finally" block does not run. That code is effectively unreachable now. But what happens if an exception is thrown in the "try" block. The "finally" should still be called as the next example demonstrates. I have included a "catch" to absorb the exception for the sake of the example.


try
{
Console.WriteLine("Example 3: Try called");
throw new Exception("Example 3: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 3: Exception Caught: ErrMsg={0}", ex.Message);
}
finally
{
Console.WriteLine("Example 3: Finally called");
}
Console.WriteLine("Example 3: More code called");


You may have noticed that the last line of code following the finally block executes this time, but note the order. First the "try" executes, then the "catch" executes, then the "finally" and then the code following the "finally" block. Again, "finally" works as advertised. We could also "return" from the "catch" block and "finally" will still run.


try
{
Console.WriteLine("Example 4: Try called");
throw new Exception("Example 4: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 4: Exception Caught: ErrMsg={0}", ex.Message);
return;
}
finally
{
Console.WriteLine("Example 4: Finally called");
}
Console.WriteLine("Example 4: More code called");


... and of course, the line following the "finally" block does not execute because of the return. You say, "yeh, but you are catching the exception, what if you don't catch the exception?" I'm glad you asked. This next example makes use of a helper function to demonstrate just that. In the helper function, there is a "try/finally" that throws an exception in the "try". The exception is not caught in that scope and context. I've added a catch higher up in the stack to allow you to prove these examples in a debugger. You could do without the higher catch if you want to compile and run the application outside of debug mode. But, everything is eventually caught, even if it is by the run-time. Regardless, here is the example.


try
{
button5helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 5: Outer catch: ErrMsg={0}",ex.Message);
return;
}
...
private void button5helper()
{
try
{
Console.WriteLine("Example 5: Try called");
throw new Exception("Example 5: Exception thrown");
}
finally
{
Console.WriteLine("Example 5: Finally called");
}
Console.WriteLine("Example 5: More code called");
}


The example will run "try", then "finally", then the "catch" in the outer context. The last line after the "finally" block is not run. But you ask, "what happenens when the "finally" throws an exception?" This next example will show you.


try
{
button6helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 6: Outer catch: ErrMsg={0}",ex.Message);
return;
}
...
private void button6helper()
{
try
{
Console.WriteLine("Example 6: Try called");
throw new Exception("Example 6: Exception thrown");
}
finally
{
Console.WriteLine("Example 6: Finally called");
throw new Exception("Example 6: Exception thrown by finally");
}
Console.WriteLine("Example 6: More code called");
}


Here, the "try" is called wich throws an exception, which results in the finally being called which throws an exception, followed by the "catch" in the outer context being called. But, ooh! I guess nothing's perfect. Look a the printout from the "catch". It says that it caught the exception thrown by "finally", so what happened to the original exception? Well, it's lost, just like if your "catch" logic were to throw an unintended exception. The only difference is that if your catch logic intends to throw a new exception, it would have the original exception to work with, the "finally" does not. Suffice it to say, "finally" should never "intend" to throw an exception, nor should it be used to contain the bulk of your logic (where exceptions are more likely to lurk).

Our next example shows "finally" being called after "try" throws an exception and "catch" rethrows the exception. Then "finally" also throws an exception.


try
{
button7helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 7: Outer catch: ErrMsg={0}",ex.Message);
return;
}
...
private void button7helper()
{
try
{
Console.WriteLine("Example 7: Try called");
throw new Exception("Example 7: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 7: Catch caught: ErrMsg={0}",ex.Message);
Console.WriteLine("Example 7: Catch rethrows exception");
throw ex;
}
finally
{
Console.WriteLine("Example 7: Finally called");
throw new Exception("Example 7: Exception thrown by finally");
}
}


As expected, the try runs, then the catch runs, then the finally runs, then the outer context catch runs. The outer context can only recover the exception info from the "finally".

We haven't touched on threads here. But if its a child thread you are worried about, don't. The try/finally construct is implemented in the compiler and is thread safe. If your thread catches any exceptions, the behavior should be the same as in these examples. But, if your thread lets the exception go un-caught, then the parent thread gets the exception and will have a hard time dealing with it. But, the child thread will at least have cleaned up in the "finally" before the parent dies an ungraceful death.

Oh, and what about the C++ "try/except" construct? This is a C# blog, but I will answer that one, too. The "except" construct is only implemented in C++ and VB (under the name "catch"). It is called a User-Filtered Exception and allows higher level contexts to insert specialized handling into lower level exceptions. In so doing, they insert extra programming from outside of the try/finally context that your code is probably not aware of. It kind of violates the whole purpose of "finally". But that's a C++ and VB problem that C# doesn't have (yet). And the issue and solution are well documented.

Anyhow, here's the example listing. Just create a new Window Forms application, drop 7 buttons on the form, then open the form's code and replace it all with the following...


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace TryCatchFinallyExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
// A simple try finally, the try will run, then the finally, then the remaining code
try
{
Console.WriteLine("Example 1: Try called");
}
finally
{
Console.WriteLine("Example 1: Finally called");
}
Console.WriteLine("Example 1: More code called");
}

private void button2_Click(object sender, EventArgs e)
{
// Now the try returns, never reaching the extra code, but finally still runs
try
{
Console.WriteLine("Example 2: Try called");
return;
}
finally
{
Console.WriteLine("Example 2: Finally called");
}
Console.WriteLine("Example 2: More code called");
}

private void button3_Click(object sender, EventArgs e)
{
// Now the try throws an exception, the exception is caught, finally
// runs, additional code runs
try
{
Console.WriteLine("Example 3: Try called");
throw new Exception("Example 3: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 3: Exception Caught: ErrMsg={0}", ex.Message);
}
finally
{
Console.WriteLine("Example 3: Finally called");
}
Console.WriteLine("Example 3: More code called");

}

private void button4_Click(object sender, EventArgs e)
{
// Now the try throws an exception, the exception is caught and return called,
// but finally still runs, however additional code does not run
try
{
Console.WriteLine("Example 4: Try called");
throw new Exception("Example 4: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 4: Exception Caught: ErrMsg={0}", ex.Message);
return;
}
finally
{
Console.WriteLine("Example 4: Finally called");
}
Console.WriteLine("Example 4: More code called");
}

private void button5_Click(object sender, EventArgs e)
{
try
{
button5helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 5: Outer catch: ErrMsg={0}",ex.Message);
return;
}
}
private void button5helper()
{
// Now the try throws an exception, but the exception is not caught, finally
// runs, additional code does not
try
{
Console.WriteLine("Example 5: Try called");
throw new Exception("Example 5: Exception thrown");
}
finally
{
Console.WriteLine("Example 5: Finally called");
}
Console.WriteLine("Example 5: More code called");
}

private void button6_Click(object sender, EventArgs e)
{
try
{
button6helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 6: Outer catch: ErrMsg={0}", ex.Message);
return;
}
}
private void button6helper()
{
// Now the try throws an exception, but the exception is not caught, finally
// runs amd also throws and exceptoin, additional code does not run
try
{
Console.WriteLine("Example 6: Try called");
throw new Exception("Example 6: Exception thrown");
}
finally
{
Console.WriteLine("Example 6: Finally called");
throw new Exception("Example 6: Exception thrown by finally");
}
Console.WriteLine("Example 6: More code called");

}

private void button7_Click(object sender, EventArgs e)
{
try
{
button7helper();
}
catch (Exception ex)
{
Console.WriteLine("Example 7: Outer catch: ErrMsg={0}",ex.Message);
return;
}
}
private void button7helper()
{
// Now the try throws an exception, the exception is caught but catch
// throws an exception, finally runs and also throws and exception,
// additional code does not run
try
{
Console.WriteLine("Example 7: Try called");
throw new Exception("Example 7: Exception thrown");
}
catch (Exception ex)
{
Console.WriteLine("Example 7: Catch caught: ErrMsg={0}",ex.Message);
Console.WriteLine("Example 7: Catch rethrows exception");
throw ex;
}
finally
{
Console.WriteLine("Example 7: Finally called");
throw new Exception("Example 7: Exception thrown by finally");
}

}
}
}

No comments: