STRUCTURED ERROR HANDLING

Visual Basic .NET uses the Try Catch block to provide structured error handling. The syntax is as follows:

Try
    try_statements ...
[Catch ex As exception_type_1
    exception_statements_1 ...
]
[Catch ex As exception_type_2
    exception_statements_2 ...
]
...
[Catch
    final_exception_statements ...
]
[Finally
    finally_statements ...
]
End Try

The program executes the code in the try_statements block. If any of that code throws an exception, the program jumps to the first Catch statement.

If the exception matches exception_type_1, the program executes the code in exception_statements_1. The exception type might match the Catch statement’s exception class exactly, or it might be a subclass of the listed class. For example, suppose that the code in the try_statements block performs a calculation that divides by zero. That raises a DivideByZeroException. That class inherits from the ArithmeticException class, which inherits from SystemException, which inherits from Exception. That means the code would stop at the first Catch statement it finds that looks for DivideByZeroException, ArithmeticException, SystemException, or Exception.

If the raised exception does not match the first exception type, the program checks the next Catch statement. The program keeps comparing the exception to Catch statements until it finds one that applies, or it runs out of Catch statements.


CATCH CONTROL
Arrange Catch statements so the most specific come first. Otherwise, a more general statement will catch errors before a more specific statement has a chance. For example, the generic Exception class matches all other exceptions, so if the first Catch statement catches Exception, no other Catch statements will ever execute.
If two Catch statements are unrelated, neither will catch the other’s exceptions, so put the exception more likely to occur first. That will make the code more efficient because it looks for the most common problems first. It also keeps the code that is most likely to execute near the top where it is easier to read.

If no Catch statement matches the exception, the exception “bubbles up” to the next level in the call stack and Visual Basic moves to the routine that called the current one. If that routine has appropriate error-handling code, it deals with the error. If that routine can’t catch the error, the exception bubbles up again until Visual Basic eventually either finds error-handling code that can catch the exception or runs off the top of the call stack. If it runs off the call stack, Visual Basic calls the global UnhandledException event handler described in the previous section, if one exists. If there is no UnhandledException event handler, the program crashes.

If you include a Catch statement with no exception type, that block matches any exception. If the raised exception doesn’t match any of the previous exception types, the program executes the final_exception_statements block of code. Note that the statement Catch ex As Exception also matches all exceptions, so it’s just as good as Catch by itself. It also gives you easy access to the exception object’s properties and methods.

You can figure out what exception classes to use in Catch statements in several ways. First, you can spend a lot of time digging through the online help. An easier method is to let the program crash and then look at the error message it produces. Figure 18-1 shows the error message a program throws when it tries to convert the non-numeric string Hello into an integer with Integer.Parse. From the exception dialog box’s title, it’s easy to see that the program should look for a FormatException.

FIGURE 18-1: When a program crashes, the message it generates tells you the type of exception it raised.

image

Another way to decide what types of exceptions to catch is to place a final generic Catch ex As Exception statement at the end of the Catch list. Place code inside that Catch block that displays either the exception’s type name (use TypeName) or the result of its ToString method. When you encounter new exception types, you can give them their own Catch statements and take action that’s appropriate to that exception type.


CATCH CATASTROPHES
It may not be possible to take meaningful action when you catch certain exceptions. For example, if a program uses up all of the available memory, Visual Basic throws an OutOfMemoryException. If there is no memory available, you may have trouble doing anything useful. Similarly, if there’s a problem with the filesystem, you may be unable to write error descriptions into a log file.

After it has finished running the code in try_statements and it has executed any necessary exception code in a Catch block, the program executes the code in finally_statements. The statements in the Finally section execute whether the code in try_statements succeeds or fails.

You do not need to include any Catch statements in a Try block, but leaving them all out defeats the Try block’s purpose. If the try_statements raise an error, the program doesn’t have any error code to execute, so it sends the error up the call stack. Eventually, the program finds an active error handler or the error pops off the top of the stack and the program crashes. You may as well not bother with the Try block if you aren’t going to use any Catch sections.

A Try block must include at least one Catch or Finally section, although those sections do not need to contain any code. For example, the following Try block calls subroutine DoSomething and uses an empty Catch section to ignore any errors that occur:

Try
    DoSomething()
Catch
End Try

Example program ThrowError, which is available for download on the book’s website, shows how a program can use a Try Catch block to handle errors.

Exception Objects

When a Catch statement catches an exception, its exception variable contains information about the error that raised the exception. Different exception classes may provide different features, but they all provide the basic features defined by the Exception class from which they are all derived. The following table lists the most commonly used Exception class properties and methods.

ITEM PURPOSE
InnerException The exception that caused the current exception. For example, suppose that you write a tool library that catches an exception and then throws a new custom exception describing the problem in terms of your library. You should set InnerException to the exception that you caught before you threw the new exception.
Message Returns a brief message that describes the exception.
Source Returns the name of the application or object that threw the exception.
StackTrace Returns a string containing a stack trace giving the program’s location when the error occurred.
TargetSite Returns the name of the method that threw the exception.
ToString Returns a string describing the exception and including the stack trace.

Example program ShowExceptionInfo, which is available for download on the book’s website, displays an exception’s Message, StackTrace, and ToString values.

At a minimum, the program should log or display the Message value for any unexpected exceptions so you know what exception occurred. It might also log the StackTrace or the result of ToString so you can see where the exception occurred.

The StackTrace and ToString values can help developers find a bug, but they can be intimidating to end users. Even the abbreviated format used by the exception’s Message property is usually not very useful to a user. When the user clicks the Find Outstanding Invoices button, the message “Attempted to divide by zero” doesn’t really tell the user what the problem is or what to do about it.

When a program catches an error, a good strategy is to record the full ToString message in a log file or e-mail it to a developer. Then display a message that restates the error message in terms that the user can understand. For example, the program might say the following: “Unable to total outstanding invoices. A bug report has been sent to the development team.” The program should then try to continue as gracefully as possible. It may not be able to finish this calculation, but it should not crash, and it should allow the user to continue working on other tasks if possible.

Throwing Exceptions

In addition to catching exceptions, your program may need to generate its own exceptions. Because handling an exception is called catching it, raising an exception is called throwing it. (This is just a silly pun. People also catch lions and colds, but I don’t think many people throw them. It’s as good a term as any, however.)

To throw an exception, the program creates an instance of the type of exception it wants to generate, passing the constructor additional information describing the problem. The program can then set other exception fields if you like. For example, it might set the exception’s Source property to tell any other code that catches the error where it originated. The program then uses the Throw statement to raise the error. If an error handler is active somewhere in the call stack, Visual Basic jumps to that point and the error handler processes the exception.

Example program DrawableRect, which is available for download on the book’s website, uses the following code to show how the DrawableRectangle class can protect itself against invalid input:

Public Class DrawableRectangle
    Public Sub New(new_x As Integer, new_y As Integer,
     new_width As Integer, new_height As Integer)
         ' Verify that new_width > 0.
         If new_width <= 0 Then
             Dim ex As New ArgumentException(
                 "DrawableRectangle must have a width greater than zero",
                 "new_width")
             Throw ex
         End If
 
         ' Verify that new_height> 0.
         If new_height < = 0 Then
             Throw New ArgumentException(
                 "DrawableRectangle must have a height greater than zero",
                 "new_height")
         End If
         ' Save the parameter values.
         ...
    End Sub
    ...
End Class

The class’s constructor takes four arguments: an X and Y position, and a width and height. If the width is less than or equal to zero, the program creates a new ArgumentException object. It passes the exception’s constructor a description string and the name of the argument that is invalid. After creating the exception object, the program uses the Throw statement to raise the error. The code checks the object’s new height similarly, but it creates and throws the exception in a single statement to demonstrate another style for throwing an error.

The following code shows how a program might use a Try block to protect itself while creating a new DrawableRectangle object:

Try
    Dim rect As New DrawableRectangle(10, 20, 0, 100)
Catch ex As Exception
    MessageBox.Show(ex.Message)
End Try

When your application needs to throw an exception, it’s easiest to use an existing exception class. There are a few ways to get lists of exception classes so that you can find one that makes sense for your application. Appendix O, “Useful Exception Classes,” lists some of the more useful exception classes. The online help topic, “Introduction to Exception Handling in Visual Basic .NET” at http://msdn.microsoft.com/aa289505.aspx also has a good list of exception classes at the end. Microsoft’s web page http://msdn.microsoft.com/system.exception_derivedtypelist.aspx provides a very long list of exception classes that are derived from the System.Exception class.

Another method for finding exception classes is to open the Object Browser (select the View menu’s Object Browser command) and search for “Exception.”

When you throw exceptions, you must use your judgment about selecting these classes. For example, Visual Basic uses the System.Reflection.AmbiguousMatchException class when it tries to bind a subroutine call to an object’s method and it cannot determine which overloaded method to use. This happens at a lower level than your program will act, so you won’t use that class for exactly the same purpose but it still may be useful to throw that exception. For example, if your program parses a string and, based on the string, cannot decide what action to take, you might use this class to represent the error, even though you’re not using it exactly as it was originally intended.

Be sure to use the most specific exception class possible. Using more generic classes such as Exception makes it much harder for developers to understand and locate an error. If you cannot find a good, specific fit, create your own exception class as described in the section “Custom Exceptions” later in this chapter.

Re-throwing Exceptions

Sometimes when you catch an exception, you cannot completely handle the problem. In that case, it may make sense to re-throw the exception so a routine higher up in the call stack can take a crack at it.

To re-throw an error exactly as you caught it, simply use the Throw keyword as in the following example:

Try
    ' Do something hard here.
    ...
 
Catch ex As ArithmeticException
    ' We can handle this exception. Fix it.
    ...
 
Catch ex As Exception
    ' We don't know what to do with this one. Re-throw it.
    Throw
End Try

If your code can figure out more or less why an error is happening but it cannot fix it, it’s sometimes a good idea to re-throw the error as a different exception type. For example, suppose a piece of code causes an ArithmeticException but the underlying cause of the exception is an invalid argument. In that case it is better to throw an ArgumentException instead of an ArithmeticException because that will provide more specific information higher up in the call stack.

At the same time, however, you don’t want to lose the information contained in the original ArithmeticException.

The solution is to throw a new ArgumentException but place the original ArithmeticException in its InnerException property so code that catches the new exception has access to the original information.

The following code demonstrates this technique:

Try
    ' Do something hard here.
    ...
Catch ex As ArithmeticException
    ' This was caused by an invalid argument.
    ' Re-throw it as an ArgumentException.
    Throw New ArgumentException("Invalid argument X in function Whatever.", ex)
 
Catch ex As Exception
    ' We don't know what to do with this one. Re-throw it.
    Throw
End Try

Custom Exceptions

When your application needs to raise an exception, it’s easiest to use an existing exception class. Reusing existing exception classes makes it easier for developers to understand what the exception means. It also prevents exception proliferation, where the developer needs to watch for dozens or hundreds of types of exceptions.

Sometimes, however, the predefined exceptions don’t fit your needs. For example, suppose that you build a class that contains data that may exist for a long time. If the program tries to use an object that has not refreshed its data for a while, you want to raise some sort of “data expired” exception. You could squeeze this into the System.TimeoutException class, but that exception doesn’t quite fit this use.

Building a custom exception class is easy. Make a new class that inherits from the System.ApplicationException class. Then, provide constructor methods to let the program create instances of the class. That’s all there is to it.

By convention, an exception class’s name should end with the word Exception. Also by convention, you should provide at least three overloaded constructors for developers to use when creating new instances of the class. (For more information on what constructors are and how to define them, see the section “Class Instantiation Details” in Chapter 23, “Classes and Structures.”)

The first constructor takes no parameters and initializes the exception with a default message describing the general type of error.

The other two versions take as parameters an error message, and an error message plus an inner exception object. These constructors pass their parameters to the base class’s constructors to initialize the object appropriately.

For completeness, you can also make a constructor that takes as parameters a SerializationInfo object and a StreamingContext object. This version can also pass its parameters to a base class constructor to initialize the exception object, so you don’t need to do anything special with the parameters. This constructor is useful if the exception will be serialized and deserialized. If you’re not sure whether you need this constructor, you probably don’t. If you do include it, however, you will need to import the System.Runtime.Serialization namespace in the exception class’s file to define the SerializationInfo and StreamingContext classes.

Example program CustomException, which is available for download on the book’s website, uses the following code to define the ObjectExpiredException class:

Imports System.Runtime.Serialization
 
Public Class ObjectExpiredException
    Inherits System.ApplicationException
 
    ' No parameters. Use a default message.
    Public Sub New()
        MyBase.New("This object has expired")
    End Sub
 
    ' Set the message.
    Public Sub New(new_message As String)
        MyBase.New(new_message)
    End Sub
 
    ' Set the message and inner exception.
    Public Sub New(new_message As String,
     ByVal inner_exception As Exception)
        MyBase.New(new_message, inner_exception)
    End Sub
 
    ' Include SerializationInfo object and StreamingContext objects.
    Public Sub New(info As SerializationInfo, context As StreamingContext)
        MyBase.New(info, context)
    End Sub
End Class

After you have defined the exception class, you can throw and catch it just as you can throw and catch any exception class defined by Visual Basic. For example, the following code throws an ObjectExpiredException error:

Throw New ObjectExpiredException("This Customer object has expired.")

The parent class System.ApplicationException automatically handles the object’s Message, StackTrace, and ToString properties so you don’t need to implement them yourself.

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

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