You’re uncertain how to best organize your code to handle errors at the method level. In particular, you’d like to take advantage of .NET structured exception handling for dealing with errors, but you’re not sure how to best implement it.
Use a combination of Try...Catch
blocks as
a retry mechanism for error handling.
Create and throw a new exception with the added information.
Perform it in the finally
block.
Recovery should be handled by the calling routine and its error-handling structure.
Because .NET structured exception handling is so good, we recommend that you use it, or at least consider using it, with every method that you write. There are a number of ways to implement its functionality.
To begin with, here is
the syntax of
a .NET Try...Catch...Finally
block in VB and C#:
Private Sub anyRoutine( )Try
'Routine code in this block
Catch err As Exception
'error handling in this block
Finally
'cleanup performed in this block
End Try
End Sub 'anyRoutine private void anyRoutine( ) {try
{
// Routine code in this block
}
catch (Exception err)
{
// error handling in this block
}
finally
{
// cleanup performed in this block
}
} // anyRoutine
The try
block includes code that implements the
method.
The catch
block, which is optional, includes code
to handle specific errors that you have identified as likely and to
recover from them when that is possible.
The finally
block code, which is also optional,
performs any cleanup required on leaving the method, whether due to
an error or not. This typically includes the closing of any open
database connections and files, disposing of objects created by the
method, and so on. A finally
block is guaranteed
to be executed, even if an exception is thrown or the code in the
routine performs a return.
As noted, the catch
and finally
blocks are optional. There are times when you’ll
want to use one or the other, and times when you’ll
want to use both.
Developers should make use of .NET exception handling in any method where an error is possible, but the exact technique depends on the circumstances, as summarized in Table 7-1.
Table 7-1. Guidelines for Try...Catch...Finally blocks
Can errors occur? |
Recoverable? |
Can useful context information be added? |
Cleanup required? |
Recommended combination of |
---|---|---|---|---|
No |
N/A |
N/A |
No |
None |
No |
N/A |
N/A |
Yes |
|
Yes |
No |
No |
No |
None |
Yes |
No |
No |
Yes |
|
Yes |
No |
Yes |
No |
|
Yes |
No |
Yes |
Yes |
|
Yes |
Yes |
N/A |
N/A |
|
The .NET Framework does not close database connections, files, and so
on when an error occurs. This is your responsibility as a programmer,
and you should do it in the finally
block. The
finally
block is the last opportunity to perform
any cleanup before the exception-handling infrastructure takes
control of the application.
To help you properly implement error handling in a routine,
we’ve provided the following leading questions. Your
answers can help you determine what portions of the
Try...Catch...Finally
block are needed. Refer to
Table 7-1 for how to structure a routine based on
your answers.
If not, no special error-handling code is required. Do not
shortchange the answer to this question, however, because even
x
= x + 1
can result in an
overflow exception.
If an error occurs but nothing useful can be done in the routine, the exception should be allowed to propagate to the calling routine. It serves no useful purpose to catch the exception and simply rethrow it. Bear in mind that this question is different from, “Are the potential errors recoverable at the application level?” For example, if the routine attempts to write a record to a database and finds the record locked, a retry can be attempted in the routine. However, if a value is passed to the routine and the operations on the value result in an overflow or other error, recovery cannot be performed in the routine but should be handled by the calling routine and its error-handling structure.
Exceptions that occur in the .NET Framework contain detailed information regarding the actual error. However, the exceptions do not provide any context information about what was being attempted at the application level that may assist in troubleshooting the error or providing more useful information to the user.
A new exception can be created and thrown with the added information.
The first parameter for the new exception object should contain the
useful context message, while the second parameter should be the
original exception. The exception-handling mechanisms in the .NET
Framework create a linked list of Exception
objects to create a trail from the root of the exception up to the
level where the exception is actually handled. By passing the
original exception as the second parameter, the linked list from the
root exception is maintained. For example:
Catch err As Exception Throw New Exception("Useful context message", _ err) catch (Exception err) { throw (new Exception("Useful context message", err)); }
Try...Catch
blocks warranted? A combination of Try...Catch
blocks can be useful
for providing a retry mechanism for error handling, as shown in Example 7-1 (VB) and Example 7-2 (C#).
These examples show the use of an internal
Try...Catch
block within a
while
loop to provide a retry mechanism and an
overall Try...Finally
block to ensure cleanup is
performed.
The catch
block should not be used for normal
program flow. Normal program flow code should only be placed in the
try
block, with the abnormal flow being placed in
the catch
block. Using the
catch
block in normal program flow will result in
significant performance degradation due to the complex operations
being performed by the .NET Framework to process exceptions.
The exception-handling mechanisms in the .NET Framework are extremely powerful. Whereas this example just touches on exception handling at the method level, other specific exception types can be caught and processed differently. In addition, you can create new exception classes by inheriting from the base exception classes and adding the functionality required by your applications. An example of this technique is shown in Recipe 7.4.
Example 7-1. Retrying when an exception occurs (.vb)
Private Sub updateData(ByVal problemID As Integer, _ ByVal sectionHeading As String)Const MAX_RETRIES As Integer = 5
Dim dbConn As OleDbConnection Dim dCmd As OleDbCommand Dim strConnection As String Dim strSQL As String Dim updateOK As Boolean Dim retryCount As Integer Try 'get the connection string from web.config and open a connection 'to the database strConnection = _ ConfigurationSettings.AppSettings("dbConnectionString") dbConn = New OleDbConnection(strConnection) dbConn.Open( ) 'build the update SQL to update the record in the database strSQL = "UPDATE EditProblem " & _ "SET SectionHeading='" & sectionHeading & "' " & _ "WHERE EditProblemID=" & problemID.ToString( ) dCmd = New OleDbCommand(strSQL, _ dbConn)'provide a loop with a try catch block to facilitate retrying
'the database update
updateOK = False
retryCount = 0
While ((Not updateOK) And (retryCount < MAX_RETRIES))
Try
dCmd.ExecuteNonQuery( )
updateOK = True
Catch exc As Exception
retryCount += 1
If (retryCount >= MAX_RETRIES) Then
'throw a new exception with a context message stating that
'the maximum retries was exceeded
Throw New Exception("Maximum retries exceeded", _
exc)
End If
End Try
End While
Finally 'cleanup If (Not IsNothing(dbConn)) Then dbConn.Close( ) End If End Try End Sub 'updateData
Example 7-2. Retrying when an exception occurs (.cs)
private void updateData(int problemID, String sectionHeading) {const int MAX_RETRIES = 5;
OleDbConnection dbConn = null; OleDbCommand dCmd = null; String strConnection = null; String strSQL = null; bool updateOK; int retryCount; try { // get the connection string from web.config and open a connection // to the database strConnection = ConfigurationSettings.AppSettings["dbConnectionString"]; dbConn = new OleDbConnection(strConnection); dbConn.Open( ); // build the update SQL to update the record in the database strSQL = "UPDATE EditProblem " + "SET SectionHeading='" + sectionHeading + "' " + "WHERE EditProblemID=" + problemID.ToString( ); dCmd = new OleDbCommand(strSQL, dbConn);// provide a loop with a try catch block to facilitate retrying
// the database update
updateOK = false;
retryCount = 0;
while ((!updateOK) & (retryCount < MAX_RETRIES))
{
try
{
dCmd.ExecuteNonQuery( );
updateOK = true;
} // try
catch (Exception exc)
{
retryCount ++;
if (retryCount >= MAX_RETRIES)
{
// throw a new exception with a context message stating that
// the maximum retries was exceeded
throw new Exception("Maximum retries exceeded",
exc);
}
} // catch
} // While
} // try finally { // cleanup if (dbConn != null) { dbConn.Close( ); } } // finally } // updateData