Title: Understand when a finally block is executed in C#
Many programmers don't understand (and many have forgotten) how the try catch finally block works.
The basic syntax is:
try
{
// Statements that could cause an error...
}
catch (Exception ex)
{
// Statements to execute if there is an error...
}
finally
{
// Statements to execute after the other statements are done...
}
You can have multiple catch blocks that look for different kinds of exceptions. You can even have a catch block that doesn't declare any kind of exception variable, although then you can't easily look at the exception, for example to display the exception's error message.
The statement must include at least one catch block or a finally block, but it doesn't need to include both. For example, you can have a finally block and no catch blocks.
The focus of this post is on when the finally block is executed. It is always executed when the try statements and possibly catch statements are finished, no matter how the code exits those statements. This example demonstrates some of the many ways code could exit a try-catch-finally block and shows that the finally block is executed in every case. (Almost. More on that a bit later.)
The following code shows all of the cases executed by this example. Comments before each event handler show the results displayed in the program's ListBox. I know this is a lot of code but it's quite simple and mostly self-explanatory.
// Run normally without errors.
// Produces: Statement
// Finally
private void btnNormal_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
try
{
// No error here.
lstResults.Items.Add("Statement");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
}
finally
{
lstResults.Items.Add("Finally");
}
}
// Use a break statement.
// Produces: Statement
// About to break
// Finally
private void btnBreak_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
for (; ; )
{
try
{
// No error here.
lstResults.Items.Add("Statement");
// Break.
lstResults.Items.Add("About to break");
break;
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
}
finally
{
lstResults.Items.Add("Finally");
}
}
}
// Use a continue statement.
// Produces: Statement
// About to continue
// Finally
private void btnContinue_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
do
{
try
{
// No error here.
lstResults.Items.Add("Statement");
// Continue.
lstResults.Items.Add("About to continue");
continue;
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
}
finally
{
lstResults.Items.Add("Finally");
}
lstResults.Items.Add("Never gets here");
} while (false);
}
// Throw an error.
// Produces: Statement
// Error: Specified argument was out of the range of
// valid values.
// Finally
private void btnError_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
try
{
lstResults.Items.Add("Statement");
// Throw an error.
throw new ArgumentOutOfRangeException();
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
}
finally
{
lstResults.Items.Add("Finally");
}
}
// Return.
// Produces: Statement
// Finally
private void btnReturn_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
try
{
lstResults.Items.Add("Statement");
// Return.
return;
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
}
finally
{
lstResults.Items.Add("Finally");
}
}
// Throw an error and then another in the catch block.
// Produces: Statement
// Error: Specified argument was out of the range of
// valid values.
// Nested Error: Specified argument was out of the
// range of valid values.
// Nested Finally
// Finally
private void btn2Errors_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
try
{
lstResults.Items.Add("Statement");
// Throw an error.
throw new ArgumentOutOfRangeException();
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
// Throw another error.
try
{
throw new ArgumentOutOfRangeException();
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception inner_ex)
{
lstResults.Items.Add("Nested Error: " + inner_ex.Message);
}
finally
{
lstResults.Items.Add("Nested Finally");
}
}
finally
{
lstResults.Items.Add("Finally");
}
}
// Throw an error and return from within the catch block.
// Produces: Statement
// Error: Specified argument was out of the range of
// valid values.
// Finally
private void btnErrorReturn_Click(object sender, EventArgs e)
{
lstResults.Items.Clear();
try
{
lstResults.Items.Add("Statement");
// Throw an error.
throw new ArgumentOutOfRangeException();
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
catch (Exception ex)
{
lstResults.Items.Add("Error: " + ex.Message);
// Return.
return;
// The compiler knows that the following code is unreachable.
lstResults.Items.Add("Never gets here");
}
finally
{
lstResults.Items.Add("Finally");
}
}
// Use Application.Exit to end the application.
// This closes the window so this method displays
// output in the console window.
// Produces: Statement
// The code actually gets here!
// Finally
private void btnExit_Click(object sender, EventArgs e)
{
try
{
Console.WriteLine("Statement");
// Exit.
Application.Exit();
Console.WriteLine("The code actually gets here!");
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
finally
{
// The code does get here but the window
// goes away so you can't see messages there.
Console.WriteLine("Finally");
}
}
// Use Environment.Exit to end the application.
// This closes the window so this method displays
// output in the console window.
// Produces: Statement
private void btnEnvExit_Click(object sender, EventArgs e)
{
try
{
Console.WriteLine("Statement");
// Exit.
Environment.Exit(0);
Console.WriteLine("The code actually gets here!");
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
finally
{
// The code does get here but the window
// goes away so you can't see messages there.
Console.WriteLine("Finally");
}
}
There are two interesting points to note here. First and most importantly, the finally block is always executed in every one of these cases except for Environment.Exit. Many programmers assume statements like return, break, and continue will immediately jump execution to a new location without any intervening steps, but in fact the finally block is executed in every case.
The second interesting point is that the finally block is even executed if you use Application.Exit to immediately stop the program. Environment.Exit, however, halts the program immediately without executing the finally block. It is the only method shown here that doesn't execute that block.
For that reason, and because it prevents forms from executing their FormClosing and other cleanup events, I recommend that you never use Environment.Exit to end a program. The most eco-friendly way to end a program is to close all of its forms. That gives all of the forms a chance to clean up before they go away.
Download the example to experiment with it and to see additional details.
|