Handling Exceptions

Structured exception handling is based around the idea that while exceptions should be used for unexpected conditions, they can be built within your application structure. Some older languages would allow for generic error handling that didn't exist within a defined set of boundaries. However, professional developers learned long ago that even unexpected conditions should be definable within your application structure.

To allow for this you may have what is known as a last-chance error handler at the topmost level of your application; however, most error handling is structured within individual modules. Within Visual Basic error handling depends on four keywords. Three of these are associated with properly identifying and handling exceptions, while the fourth is used when you wish to signal that an unexpected condition has occurred.

1. Try—Begins a section of code in which an exception might be generated from a code error. This section of code is often called a Try block. It is always used with one or more exception handlers.
2. Catch—Creates a standard exception handler for a type of exception. One or more Catch code blocks follow a Try block. Each Catch block must catch a different exception type. When an exception is encountered in the Try block, the first Catch block that matches that type of exception receives control. Can be omitted when a Finally block is used.
3. Finally—A handler that is always run as part of your structured exception handling. Contains code that runs when the Try block finishes normally, or when a Catch block receives control and then finishes. That is, the code in the Finally block always runs, regardless of whether an exception was detected. Typically, the Finally block is used to close or dispose of any resources, such as database connections, that might have been left unresolved by the code that had a problem.
4. Throw—Generates an exception. It can be done in a Catch block when an exception should be elevated to the calling method. Often exceptions become nexted. Can also be called in a routine that has itself detected an error, such as a bad argument passed in. For example, one common place to throw an exception is after a test on the arguments passed to a method or property. If a method discovers that an argument is missing or not valid and processing should not continue, an error can be thrown to the calling method.

The next section of the chapter covers the keywords in detail and includes code samples of the keywords in action. All the code in this section is included in the code download for this chapter.

Try, Catch, and Finally

Now is a good time to build an example of some typical, simple structured exception-handling code in Visual Basic. In this case, the most likely source of an error will be a division by 0 error.

To keep from starting from scratch, you can take the sample WPF application from Chapter 1 and use it as a baseline framework. Similar to Chapter 1, individual modules (functions) can be created, which are then called from the default button handler. For the purposes of this chapter, the application will have the name ProVB_Ch06.

Start with the following code snippet, the iItems argument. If it has a value of zero, then this would lead to dividing by zero, which would generate an exception.

    Private Function IntegerDivide(iItems As Integer, iTotal As Integer) As String
        Dim result As String
        ' Code that might throw an exception is wrapped in a Try block
        Try
            ' This will cause an exception to be thrown if iItems = 0
            result = iTotal  iItems
        Catch ex As Exception
            ' If the calculation failed, you get here
            result = "Generic exception caught" & ex.Message
        End Try
        Return result
    End Function

This code traps all exceptions using the generic type Exception, and doesn't include any Finally logic. Before running the program keep, in mind you'll be able to follow the sequence better if you place a breakpoint at the top of the IntegerDivide function and step through the lines. If you pass the value 0 in the first parameter you'll see you get a divide by zero exception. The message is returned.

The next code snippet illustrates a more complex example that handles the divide-by-zero exception explicitly. This code contains a separate Catch block for each type of handled exception. If an exception is generated, then .NET will progress through the Catch blocks looking for a matching exception type. This code helps illustrate that Catch blocks should be arranged with specific types first.

The IntegerDivide2 method also includes a Finally block. This block is always called and is meant to handle releasing key system resources like open files on the operating system, database connections, or other operating-system-level resources that are limited in availability.

    Private Function IntegerDivide2(iItems As Integer, iTotal As Integer) As String
        Dim result As String
        ' Code that might throw an exception is wrapped in a Try block
        Try
            ' This will cause an exception to be thrown if iItems = 0
            result = iTotal  iItems
        Catch ex As DivideByZeroException
            ' You'll get here with a DivideByZeroException in the Try block
            result = "Divide by zero exception caught: " & ex.Message
        Catch ex As Exception
            ' If the calculation failed, you get here
            result = "Generic exception caught: " & ex.Message
        Finally
            MessageBox.Show(
             "Always close file system handles and database connections.")
        End Try
        Return result
    End Function

Keep in mind when you run this code that the event handler will display the message box prior to updating the main display.

The Throw Keyword

Sometimes a Catch block isn't meant to fully handle an error. Some exceptions should be “sent back up the line” to the calling code. In some cases this allows the problem to be visible to the correct code to handle and possibly even log it. A Throw statement can be used to look for the next higher error handler without fully handling an error.

When used in a catch block the Throw statement ends execution of the exception handler—that is, no more code in the Catch block after the Throw statement is executed. However, Throw does not prevent code in the Finally block from running. That code still runs before the exception is kicked back to the calling routine.

Note when rethrowing an exception you have two alternatives, the first is to simply take the exception you've handled and literally throw it again. The second is to create a new exception, add your own information to that exception, and assign the original exception as an inner exception. In this way you can add additional information to the original exception to indicate where it was rethrown from.

Throw can also be used with exceptions that are created on the fly. For example, you might want your earlier function to generate an ArgumentException, as you can consider a value of iItems of zero to be an invalid value for that argument.

In such a case, a new exception must be instantiated. The constructor allows you to place your own custom message into the exception. The following code snippet illustrates all of the methods discussed related to throwing exceptions. This includes detecting and throwing your own exception as well as how to rethrow a previously occurring exception.

Private Function IntegerDivide3(iItems As Integer, iTotal As Integer) As String
    If iItems = 0 Then
        Dim argException = New  _
             ArgumentException("Number of items cannot be zero")
        Throw argException
    End If
    Dim result As String
    ' Code that might throw an exception is wrapped in a Try block
    Try
        ' This will cause an exception to be thrown if iItems = 0
        result = iTotal  iItems
    Catch ex As DivideByZeroException
        ' You'll get here with a DivideByZeroException in the Try block
        result = "Divide by zero exception caught: " & ex.Message
        Throw ex
    Catch ex As Exception
        ' If the calculation failed, you get here
        result = "Generic exception caught: " & ex.Message
        Dim myException = New Exception("IntegerDivide3: Generic Exception", ex)
        Throw myException
    Finally
        MessageBox.Show("Always close file system handles and database 
connections.")
    End Try
    Return result
End Function 

Error handling is particularly well suited to dealing with problems detected in property procedures. Property Set logic often includes a check to ensure that the property is about to be assigned a valid value. If not, then throwing a new ArgumentException (instead of assigning the property value) is a good way to inform the calling code about the problem.

The Exit Try Statement

The Exit Try statement will, under a given circumstance, break out of the Try or Catch block and continue at the Finally block. In the following example, you exit a Catch block if the value of iItems is 0, because you know that your error was caused by that problem:

    Private Function IntegerDivide4(iItems As Integer, iTotal As Integer) As String
        Dim result As String
        ' Code that might throw an exception is wrapped in a Try block
        Try
            ' This will cause an exception to be thrown if iItems = 0
            result = iTotal  iItems
        Catch ex As DivideByZeroException
            ' You'll get here with a DivideByZeroException in the Try block
            result = "Divide by zero exception caught: " & ex.Message
            ' You'll get here with a DivideByZeroException in the Try block.
            If iItems = 0 Then
                Return 0
                Exit Try
            Else
                Throw ex
            End If
        Catch ex As Exception
            ' If the calculation failed, you get here
            result = "Generic exception caught: " & ex.Message
            Dim myException = New Exception("IntegerDivide3: Generic Exception", 
                                  ex)
            Throw myException

        Finally
            MessageBox.Show(
                "Always close file system handles and database connections.")
        End Try
        Return result
    End Function

In your first Catch block, you have inserted an If block so that you can exit the block given a certain condition (in this case, if the overflow exception was caused because the value of iItems was 0). The Exit Try goes immediately to the Finally block and completes the processing there.

Now, if the overflow exception is caused by something other than division by zero, you'll get a message box displaying “Error not caused by iItems.”


Note
The best practice is to not have an exception you don't need. These examples have worked with a division-by-zero error, which can be avoided. Avoiding errors is always best practice.

A Catch block can be empty. In that case, the exception is ignored. Also remember, exception handlers don't resume execution with the line after the line that generated the error. Once an error occurs, execution resumes either the Finally block or the line after the End Try if no Finally block exists.

Sometimes particular lines in a Try block may need special exception processing. Moreover, errors can occur within the Catch portion of the Try structures and cause further exceptions to be thrown. For both of these scenarios, nested Try structures are available.

When you need code to resume after a given line of code, or if you are doing something that may throw an error within a catch block, you'll want a nested structured error handler. As the name implies, because these handlers are structured it is possible to nest the structures within any other.

Using Exception Properties

The preceding examples have all leveraged the Message property of the exception class. When reporting an error, either to a display such as a message box or to a log entry, describing an exception should provide as much information as possible concerning the problem.

The Message Property

Because in most cases the output hasn't been the focus of the discussion on structured error handling, you haven't seen many screenshots of the output. Returning to the original example, which displayed an error message, you know that the display using the message property looks similar to what is seen in Figure 6.1.

Figure 6.1 Displaying the message property

6.1

This is a reasonable error message that explains what happened without providing too much detail. While a professional would normally adjust the wording of this message, if this message was presented to a user it wouldn't cause too much of a problem.

On the other hand one of the most brutal ways to get information about an exception is to use the ToString method of the exception. Suppose that you modify the earlier example of IntegerDivide to change the displayed information about the exception, such as using ToString as follows:

Private Function IntegerDivide5(iItems As Integer, iTotal As Integer) As String
        Dim result As String
        ' Code that might throw an exception is wrapped in a Try block
        Try
            ' This will cause an exception to be thrown if iItems = 0
            result = iTotal  iItems
        Catch ex As Exception
            ' If the calculation failed, you get here
            result = ex.ToString
        End Try
        Return result
End Function

The message shown in Figure 6.2 is helpful to a developer because it contains a lot of information, but it's not something you would typically want users to see. Instead, a user normally needs to see a short description of the problem. For the user, a message that looks like string in the Message property is appropriate. On the other hand, what the ToString method returns is, in fact, the concatenation of two properties: the message and the stack trace.

Figure 6.2 Displaying the default string property of an exception

6.2

Source and StackTrace

The Source and StackTrace properties provide information regarding where an error occurred. This supplemental information can be useful. In the case of the Source property, the text returns the name of the assembly where the error occurred. You can test this by replacing the ToString method in IntegerDivide5 with the Source property and rerunning the test.

The StackTrace is typically seen as more useful. This property not only returns information about the method where the error occurred, but returns information about the full set of methods used to reach the point where the error occurred. As noted, Figure 6.2 includes a sample of what is returned when you review this property.

The InnerException

The InnerException property is used to store an exception trail. This comes in handy when multiple exceptions occur. It's quite common for an exception to occur that sets up circumstances whereby further exceptions are raised. As exceptions occur in a sequence, you can choose to stack them for later reference by use of the InnerException property of your Exception object. As each exception joins the stack, the previous Exception object becomes the inner exception in the stack.

For simplicity, we're going to copy the current IntegerDivide5 method to create an inner exception handler in a new method IntegerDivide6. In this exception handler, when the divide by zero error occurs, create a new exception, passing the original exception as part of the constructor. Then in the outer exception handler, unwind your two exceptions displaying both messages.

The following code snippet illustrates the new method IntegerDivide6, and the results of running this new method are shown in Figure 6.3.

Private Function IntegerDivide6(iItems As Integer, iTotal As Integer) As String
    Dim result As String
    ' Code that might throw an exception is wrapped in a Try block
    Try
        Try
            ' This will cause an exception to be thrown if iItems = 0
            result = iTotal  iItems
        Catch ex As Exception
             Dim myException = New Exception(
                              "IntegerDivide6: My Generic Exception",
                              ex)
            Throw myException
        End Try
        Return result
    Catch ex As Exception
        ' If the calculation failed, you get here
        result = "Outer Exception: " & ex.Message & vbCrLf
        result += "Inner Exception: " & ex.InnerException.Message
    End Try
    Return result
End Function 

Figure 6.3 Displaying the inner and outer exception messages

6.3

Figure 6.3 shows your custom message as the outer exception. Then the InnerException is referenced and the original error message, the divide-by-zero exception, is displayed.

GetBaseException

The GetBaseException method comes in very handy when you are deep in a set of thrown exceptions. This method returns the originating exception by recursively examining the InnerException until it reaches an exception object that has a null InnerException property. That exception is normally the exception that started the chain of unanticipated events.

To illustrate this, modify the code in IntegerDivide6 and replace the original Outer and Inner exception messages with a single line of code as shown in the following code snippet:

        'result = "Outer Exception: " & ex.Message & vbCrLf
        'result += "Inner Exception: " & ex.InnerException.Message
        result = "Base Exception: " & ex.GetBaseException.Message

As shown in Figure 6.4 the code traverses back to the original exception and displays only that message.

Figure 6.4 Displaying the innermost exception message

6.4

HelpLink

The HelpLink property gets or sets the help link for a specific Exception object. It can be set to any string value, but it's typically set to a URL. If you create your own exception in code, you might want to set HelpLink to a URL (or a URN) describing the error in more detail. Then the code that catches the exception can go to that link. You could create and throw your own custom application exception with code like the following:

Dim ex As New ApplicationException("A short description of the problem")
ex.HelpLink = "http://mysite.com/somehtmlfile.htm"
Throw ex

When trapping an exception, the HelpLink can be used with something like Process.Start to start Internet Explorer using the help link to take the user directly to a help page, where they can see details about the problem.

..................Content has been hidden....................

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