Structured Error Handling


Visual Basic .NET introduced structured error handling using the Try block. 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, or SystemException.

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.

Tip 

Be sure to arrange Try 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 includes all other exceptions, so if the first Try statement catches Exception, no other Try statements will ever execute.

If two Try statements are unrelated, so neither will catch the other’s exceptions, 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, then the exception bubbles up again until Visual Basic eventually either finds error-handling code that can catch the exception, or it 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 good as Catch by itself. It also gives you easy access to the exception object’s properties and methods.

There are several ways you can figure out what exception classes to use in Catch statements. 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 8-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 this message it’s easy to see that the program should look for a FormatException.

image from book
Figure 8-1: When a program crashes, the message it generates tells you the type of exception it raised.

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 more specific action appropriate to different exception types.

Tip 

In some cases, 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, then you may have trouble doing anything useful. Similarly, if there’s a problem with the file system, 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. You can use the Finally section to execute code 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 

Using an empty Finally section is legal but not terribly useful. The following code doesn’t protect the program from any exceptions and doesn’t do anything in the Finally block. You may as well just call the DoSomething subroutine without a Try block.

  Try     DoSomething() Finally End Try  

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.

Open table as spreadsheet

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 throw 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.

In the following code, the btnCalculate_Click event handler calls subroutine CalculateEmployeeSalaries within a Try block. That routine calls subroutine CheckVacationPay, which performs a calculation that divides by zero. When the program encounters the error, it looks for an active error handler in subroutine CheckVacationPay. It doesn’t find one so the code travels up the call stack to subroutine CalculateEmployeeSalaries and looks for an error handler there. Again, the program doesn’t find one, so it moves up the call stack to subroutine Calculate. There it finds an active Try block so it jumps to the block’s Catch statements. The Catch statement here looks for a generic Exception object so it matches any exception. The code displays the exception’s Message, StackTrace, and ToString values.

  Private Sub btnCalculate_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnCalculate.Click     Try         CalculateEmployeeSalaries()     Catch ex As Exception         Debug.WriteLine("***********")         Debug.WriteLine(ex.Message)         Debug.WriteLine("***********")         Debug.WriteLine(ex.StackTrace)         Debug.WriteLine("***********")         Debug.WriteLine(ex.ToString)         Debug.WriteLine("***********")     End Try End Sub Private Sub CalculateEmployeeSalaries()     CheckVacationPay() End Sub Private Sub CheckVacationPay()     Dim i As Integer     Dim j As Integer     i = 1 \ j End Sub  

The following text shows this program’s output. The exception’s Message property returns the string “Attempted to divide by zero.” The StackTrace method returned a series of strings listing the routines that called each other on the way to the error. The lines are broken to fit here but each appears on a single line in the program’s actual output. Each line shows the file and line number where the subroutine calls occurred. This program was called StackTraceTest so that name prefixes all of the routine names. For example, StackTraceTest.Form1.CheckVacationPay is the CheckVacationPay subroutine in the Form1 module in project StackTraceTest. The exception’s ToString method returned a brief message describing the error followed by a stack trace.

  *********** Attempted to divide by zero. ***********     at ShowExceptionInfo.Form1.CheckVacationPay() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 25     at ShowExceptionInfo.Form1.CalculateEmployeeSalaries() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 18     at ShowExceptionInfo.Form1.btnCalculate_Click(Object sender, EventArgs e) in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 5 *********** System.DivideByZeroException: Attempted to divide by zero.     at ShowExceptionInfo.Form1.CheckVacationPay() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 25     at ShowExceptionInfo.Form1.CalculateEmployeeSalaries() in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 18     at ShowExceptionInfo.Form1.btnCalculate_Click(Object sender, EventArgs e) in C:\Documents and Settings\Rod\Local Settings\Application Data\Temporary Projects\ShowExceptionInfo\Form1.vb:line 5 *********** 

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 recasts 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.

StackTrace Objects

An exception object’s ToString and StackTrace methods return textual representations of the program’s stack trace. Your code can also use StackTrace objects to examine the program’s execution position without generating an error.

In the following code, the btnCalculate_Click event handler calls subroutine CalculateEmployeeSalaries, which calls subroutine CheckVacationPay, which calls subroutine ShowCallStack. Sub routine ShowCallStack creates a new CallStack object, passing it the parameter True to indicate that it should generate file name and line number information for the stack trace. The CallStack object contains an ordered series of StackFrame objects that represent the programs subroutine calls. Subroutine ShowCallStack loops through the StackFrame objects, displaying the routine names, file names, and line numbers for each.

  Imports System.Diagnostics ... Private Sub btnCalculate_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnCalculate.Click     CalculateEmployeeSalaries() End Sub Private Sub CalculateEmployeeSalaries()     CheckVacationPay() End Sub Private Sub CheckVacationPay()     ShowCallStack() End Sub ' Loop through the call stack from the bottom up. Private Sub ShowCallStack()     ' Create a StackTrace object with      ' file names and line numbers.     Dim stack_trace As New System.Diagnostics.StackTrace(True)     ' Display the frame information.     For i As Integer = 0 To stack_trace.FrameCount - 1         With stack_trace.GetFrame(i)             Debug.WriteLine( _                 "Method: " & .GetMethod().ToString & _                 ", File: '" & _                 .GetFileName() & _                 "', Line: " & .GetFileLineNumber())         End With     Next i End Sub  

The following text shows the program’s output. In this example, subroutine ShowCallStack was called by CheckVacationPay, which was called by CalculateEmployeeSalaries, which was called by the btnCalculate_Click event handler. Beyond that, the code moves into the Visual Basic system code behind the scenes. Notice that the output does not include a line number or file name for the routines beyond this point. The button’s Click event handler was called by an OnClick routine. This button was triggered by clicking the mouse on the button. You can move farther up the stack to see other hidden system routines all the way through the Windows message loop and back to the call to ThreadStart, which started the program running.

  Method: Void ShowCallStack()     Line: 21, File: 'C:\VB Prog Ref\CeSrc\Ch08\ShowStackTrace\Form1.vb' Method: Void CheckVacationPay()     Line: 14, File: 'C:\VB Prog Ref\CeSrc\Ch08\ShowStackTrace\Form1.vb' Method: Void CalculateEmployeeSalaries()     Line: 10, File: 'C:\VB Prog Ref\CeSrc\Ch08\ShowStackTrace\Form1.vb' Method: Void btnCalculate_Click(System.Object, System.EventArgs)     Line: 6, File: 'C:\VB Prog Ref\CeSrc\Ch08\ShowStackTrace\Form1.vb' Method: Void OnClick(System.EventArgs)     Line: 0, File: '' Method: Void OnClick(System.EventArgs)     Line: 0, File: '' Method: Void PerformClick()     Line: 0, File: '' Method: Boolean ProcessDialogKey(System.Windows.Forms.Keys)     Line: 0, File: '' Method: Boolean ProcessDialogKey(System.Windows.Forms.Keys)     Line: 0, File: '' Method: Boolean PreProcessMessage(System.Windows.Forms.Message ByRef)     Line: 0, File: '' Method: Boolean PreTranslateMessage(MSG ByRef)     Line: 0, File: '' Method: Boolean   System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FPreTranslateMessage(   MSG ByRef)     Line: 0, File: '' Method: Boolean   System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(   Int32, Int32, Int32)     Line: 0, File: '' Method: Void RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)     Line: 0, File: '' Method: Void RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)     Line: 0, File: '' Method: Void Run(System.Windows.Forms.ApplicationContext)     Line: 0, File: '' Method: Void OnRun()     Line: 0, File: '' Method: Void DoApplicationModel()     Line: 0, File: '' Method: Void Run(System.String[])     Line: 0, File: '' Method: Void Main(System.String[])     Line: 76, File: '1.vb' Method: Int32 nExecuteAssembly(System.Reflection.Assembly, System.String[])     Line: 0, File: '' Method: Int32 ExecuteAssembly(System.String, System.Security.Policy.Evidence,   System.String[])     Line: 0, File: '' Method: Void RunUsersAssembly()     Line: 0, File: '' Method: Void ThreadStart_Context(System.Object)     Line: 0, File: '' Method: Void Run(System.Threading.ExecutionContext,   System.Threading.ContextCallback, System.Object)     Line: 0, File: '' Method: Void ThreadStart()     Line: 0, File: ''  

The StackTrace class has a ToString method that provides a similar output in a single string, although it omits the file names and line numbers. The StackFrame objects contained in the StackTrace object also have ToString methods that provide output similar to the results shown here.

Throwing Exceptions

In addition to catching exceptions, your program may need to generate its own exceptions. Because handling an exception is called catching it, logically, raising an exception must be 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, so don’t worry too much about it.)

To throw an error, 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 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.

The following code shows how the DrawableRectangle class can protect itself against invalid input. 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.

  Public Class DrawableRectangle     Public Sub New(ByVal new_x As Integer, ByVal new_y As Integer, _      ByVal new_width As Integer, ByVal new_height As Integer)         ' Verify that new_width > 0.         If new_width <= 0 Then             ' Throw an ArgumentException.             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 an ArgumentException.             Throw New ArgumentException( _                "DrawableRectangle must have a height greater than zero", _                    "new_height")         End If         ' Save the parameter values.         ...     End Sub     ' Other code for this class omitted.     ... End Class  

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 

Figure 8-2 shows the error message generated by this code. The exception class composed the message based on the description and parameter name used in the call to the exception class’s constructor.

image from book
Figure 8-2: An ArgumentException object generates its Message value from the description and parameter name passed to its constructor.

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. First, Appendix F lists some of the more useful exception classes. The online help topic, “Introduction to Exception Handling in Visual Basic .NET” at msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/vbtchexceptionserrorsinvisualbasicnet.asp also has a good list of exception classes at the end. Microsoft’s web page msdn2.microsoft.com/library/zbea8bay.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.” Figure 8-3 shows the Object Browser displaying roughly 400 matches, many of which are exception classes. The System.FormatException class is selected, so the Object Browser is showing that class’s description.

image from book
Figure 8-3: You can use the Object Browser to find exception classes.

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. It may be useful, for example, if your routine parses a string and, based on the string, cannot decide what action to take. In that case, you might use this class to represent the error, even though you’re not using it exactly as it was originally intended.

Before you use one of these classes, look it up in the online help to make sure that it fits your purpose. If there’s no good fit, you can always create your own as described in the following section, “Custom Exceptions.”

Specialized classes and libraries sometimes have their own particular exception classes. For example, serialization and cryptographic objects have their own sets of exception classes that make sense within their own domains. Usually, these are fairly specialized, so you won’t need to throw them in your program unless you are reraising an error you received from a serialization or cryptographic object.

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 quite 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. The Expired class is a better fit, but it’s part of the System.Net.Cookie namespace. Using it would require your application to include the System.Net.Cookie namespace just to define the exception class, even if the program has nothing to do with cookies.

In this case, it would be better to create your own exception class. 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. The first 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. 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.

The following code shows how you might 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(ByVal new_message As String)         MyBase.New(new_message)     End Sub     ' Set the message and inner exception.     Public Sub New(ByVal 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(ByVal info As SerializationInfo, _       ByVal 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.




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net