7.1. Handling Errors at the Method Level

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.

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.

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.

Basic syntax of Try...Catch...Finally

To begin with, here is the syntax of a .NET Try...Catch...Finally block in VB and C#:

                  Basic syntax of Try...Catch...Finally
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

Basic syntax of Try...Catch...Finally
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.

Tip

A try block must also contain either a catch or a finally block.

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

Warning

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.

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:

                           Additional considerations
Catch err As Exception
  Throw New Exception("Useful context message", _
                       err)

Additional considerations
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.

Tip

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.

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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset