7.1.1 Problem 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. 7.1.2 Solution -
- If potential errors are recoverable in the routine
-
Use a combination of Try...Catch blocks as a retry mechanism for error handling. -
- If useful information can be added to the exception
-
Create and throw a new exception with the added information. -
- If cleanup is required
-
Perform it in the finally block. -
- If potential errors are not recoverable in the routine
-
Recovery should be handled by the calling routine and its error-handling structure. 7.1.3 Discussion 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. 7.1.3.1 Basic syntax of Try...Catch...Finally 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. | A try block must also contain either a catch or a finally block. | | 7.1.3.2 Guidelines for implementing 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 try , catch , and finally | No | N/A | N/A | No | None | No | N/A | N/A | Yes | try and finally only | Yes | No | No | No | None | Yes | No | No | Yes | try and finally only | Yes | No | Yes | No | try and catch only | Yes | No | Yes | Yes | try , catch , and finally | Yes | Yes | N/A | N/A | try and catch only | | 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. | | 7.1.3.3 Additional considerations 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. -
- Can any errors occur in this routine?
-
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. -
- Are the potential errors recoverable in the routine?
-
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. -
- Can any useful information be added to the exception?
-
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)); } -
- Is a combination of 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. 7.1.4 See Also 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 |