Using Finally Blocks In Exception Handling
Nov 8, 2018 • 7 Minute Read
The Problem This Solves
While the nature of recovering from exceptions can be very specific, there tend to be certain aspects that we want to happen in every event – whether one exception or another happened, or whether the function succeeded with no problem at all.
We can arrange our code in the following broad strokes:
public static void OurProcedure()
{
// set up stuff to get ready to act in our code
try
{
// perform actions that may create exceptions
}
catch(ExceptionType1 exception1)
{
// repair the situation when ExceptionType1 occurs
// using the details available in the exception1 variable
}
catch (ExceptionType2)
{
// repair the situation when ExceptionType2 occurs
// This exception type doesn't have any details which are meaningful
// so we don't bother with an exception variable
}
finally
{
// tear down anything that needs to be disassembled
// or release any resources that are no longer needed
}
}
We begin by performing some exception-safe setup outside of the try block. This will make variables available to all of the catch and finally scope blocks that are to follow. Then we act in the try block – performing the operations that provide value for the function. Each catch block deals with problems that we imagine can arise, and finally – the FINALLY block cleans up resources that are unnecessary now that the operation is complete, whether it succeeded or failed.
Our Scenario
Our efforts to get our vendor to get their servers perfectly reliable continue – we still get a ConnectionFailedException about once a week – which, if you think about it, is really not that bad. But there is a problem with how the vendor library is implemented.
If the procedure proceeds through the happy path:
var priceClient = new PriceClient();
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
A socket, a network connection, is opened with the Connect function and closed at the end of GetPrice. But when a ConnectionFailedException occurs, in GetPrice, the socket is never closed – this means that over time, these dead connections pile up and your IT buddy is having to restart the router every few weeks to close them out.
As it happens, the vendor knows about this problem, so they make a method available on their library to fix this:
priceClient.ForceClose();
Our initial impulse is to just place this code into the catch block that's causing the problem:
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
priceClient.ForceClose();
return null;
}
But something's bothering us – the connection gets closed on the happy path, right? We think so, but are we 100% sure? Furthermore, in the meantime, our top-level error-handler has caught a few ConnectionTimeoutExceptions, where, rather than failing to connect at all, the connection succeeds but takes too long to respond:
catch (TimeoutException timeout)
{
return null;
}
I guess if we're going to handle this everywhere, we'll need to copy and paste the ForceClose() call everywhere – and we know that copy and paste is almost never the right answer.
This is what finally blocks are for. A finally block is code that is always, always, always executed, whether or not an exception occurred.
To make our ForceClose() call execute in all cases – the happy path and all of our catch blocks, we add it to the end like this:
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
return null;
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
finally
{
priceClient.ForceClose();
}
Why not simply add ForceClose() at the end outside of the error handling, like this?
try
{
priceClient.Connect();
var updatedPrice = priceClient.GetPrice();
return updatedPrice;
}
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
return null;
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
return null;
}
priceClient.ForceClose();
The problem is those return statements in the catch blocks. When the execution hits those points, it bypasses anything left in the procedure – including other catch blocks. Including the finally block ensures that your cleanup code will be executed.
You Can't Return in a Finally Block
Another thing you're doing a lot of in those catch blocks is returning the value – that's the problem with simply putting ForceClose() at the end. Maybe we should reorganize our code and just return the value in the finally block, like so:
var priceClient = new PriceClient();
decimal? updatedPrice = null;
try
{
priceClient.Connect();
updatedPrice = priceClient.GetPrice();
}
catch(InvalidCastException)
{
updatedPrice = decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
// do nothing because updatedPrice is already null
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
// do nothing because updatedPrice is already null
}
finally
{
priceClient.ForceClose();
return updatedPrice;
}
If we do this we get a compiler error:
Control cannot leave the body of a finally clause
Returning a value from the finally clause is forbidden because it sets up a situation where the return values of a function are in conflict:
try
{
return 1;
}
finally
{
return 2;
}
To get the style we want, with the control flow and logic cleanly separated, we simply place the return statement at the very end, after the finally block:
decimal? updatedPrice = null;
try
{
priceClient.Connect();
updatedPrice = priceClient.GetPrice();
}
catch(InvalidCastException)
{
updatedPrice = decimal.Parse(priceClient.ReturnedValue.Substring(1));
}
catch (TimeoutException timeout)
{
// do nothing because updatedPrice is already null
}
catch(ConnectionFailedException connectionFailed)
{
var serverName = connectionFailed.ServerName;
// log servername details here
// do nothing because updatedPrice is already null
}
finally
{
priceClient.ForceClose();
}
return updatedPrice;