9.2 Runtime Error HandlingAs we have mentioned, VB currently supports both unstructured and structured error handling. Let us first look at unstructured error handling. 9.2.1 Unstructured Error HandlingError-handling techniques that revolve around the various On Error ... statements are referred to as unstructured error-handling techniques. These techniques generally use the Err object and the Visual Basic call stack. 9.2.1.1 The Err objectVisual Basic's built-in error object, called Err , is one of the main tools for unstructured error handling. This object has several properties and methods , as shown in Tables Table 9-1 and Table 9-2, respectively. Table 9-1. Properties of the Err object
Table 9-2. Methods of the Err object
9.2.1.2 Dealing with runtime errorsVisual Basic detects a runtime error as soon as it occurs, sets the properties of the Err object, and directs the flow of execution to a location that the programmer has specified by the most recent On Error ... line. This location can be one of the following:
Let us take a closer look at each of these possibilities. 9.2.1.2.1 In-line error handlingCode execution will be "redirected" to the line following the offending line of code (that is, execution will continue immediately following the offending line) if the most recent preceding On Error statement is: On Error Resume Next This is referred to as in-line error handling . Here is an example that involves renaming a file. Note the typical use of a Select Case statement to handle the error based on the value of Err.Number. Incidentally, one way to obtain error numbers is to deliberately invoke a particular error and break execution (with a breakpoint) to examine Err.Number: Dim sOldName, sNewName As String On Error Resume Next ' Ask for an existing file name sOldName = InputBox("Enter the file name to rename") ' Ask for new name sNewName = InputBox("Enter the new file name") ' Rename file Rename("c:\" & sOldName, "c:\" & sNewName) ' Deal with error If Err( ).Number = 53 Then ' File not found error MsgBox("File " & sOldName & " not found") Exit Sub Else ' All other errors MsgBox(Err().Number & ": " & Err( ).Description) Exit Sub End If 9.2.1.2.2 Centralized error handlingWhile in-line error handling does have its uses, there is much to be said for centralizing error handling within a procedure. (This often improves readability and makes code maintenance easier.) We can direct code execution to a central error handler using the code: On Error Goto label This is outlined in the following code shell: Sub Example( ) On Error Goto ErrHandler '' If run-time error occurs here '' Visual Basic directs execution to ErrHandler Exit Sub ErrHandler: '' Code can be placed here to handle errors '' or pass them up the calls list. '' We have knowledge of Err().Number, Err( ).Description, '' and Err( ).Source. End Sub Once the On Error Goto label line is executed, we say that the error handler beginning at the label ErrHandler is active . Once code execution is directed to the error handler, there are several possibilities for dealing with the error. The most common possibility is simply to handle the error in the active error handler, perhaps by displaying an error message asking the user to take corrective action. Another common (and useful) approach is passing information about an error to the calling procedure with parameters or with the return value of the offending function. For instance, if a function is designed to rename a file, the function might return an integer error code indicating the success or failure of the operation. This is quite common among the Win32 API functions. In particular, the error code might be 0 for success, -1 if the file does not exist, -2 if the new filename is invalid, and so on. A third possibility is to pass the error to the calling procedure by invoking the Err.Raise method within the active error handler, as in: Err.Raise(Err.Number, Err.Source, Err.Description, _ Err.HelpFile, Err.HelpContext) This triggers the calling procedure's error handler (or more precisely, the next enabled error handler in the calls list). This process is called regenerating or reraising the error. Note that it is possible to deactivate an active error handler using the line: On Error Goto 0 If there is no active error handler, then VB reacts to errors just as though no error handler existed in the procedure. We describe this situation in the next section. 9.2.1.2.3 No enabled error-handlerIf there is no enabled error handler in the offending procedure, either because there is no On Error statement in the procedure or because error handling has been disabled with an On Error Goto statement, then Visual Basic automatically sends the error to the calling procedure's error handler. If the calling procedure has no error handler, the error continues up the calls list until it reaches an enabled error handler. If none is found, then Visual Basic handles the error by displaying an error message and terminating the application. 9.2.2 Structured Exception HandlingStructured exception handling uses a Try ... Catch ... Finally structure to handle errors. As we will see, VB.NET's structured exception handling is a much more object-oriented approach, involving objects of the Exception class and its derived classes. 9.2.2.1 Try...Catch...FinallyThe syntax of the Try ... Catch ... Finally construct is given here: Try tryStatements [Catch1 [ exception [As type ]] [When expression ] catchStatements1 [Exit Try] Catch2 [ exception [As type ]] [When expression ] catchStatements2 [Exit Try] . . . Catchn [ exception [As type ]] [When expression ] catchStatementsn ] [Exit Try] [Finally finallyStatements ] End Try The tryStatements (which are required) constitute the Try block and are the statements that are monitored for errors by VB. Within the Try block, we say that error handling is active . The Catch blocks (of which there can be more than one) contain code that is executed in response to VB "catching" a particular type of error within the Try block. Thus, the Catch blocks consist of the error handlers for the Try block. The phrases exception [As type ] and [When expression ] are referred to as filters in the VB.NET documentation. In the former case, exception is either a variable of type Exception, which is the base class that "catches" all exceptions, or a variable of one of Exception's derived classes. (We provide a list of these classes a bit later.) For instance, the variable declared as: Catch e As Exception will catch (that is, handle) any exception. The variable declared as: Catch e As ArgumentNullException catches (handles) any exception of class ArgumentNullException. In short, type is the name of one of the exception classes. The When filter is typically used with user-defined errors. For instance, the code in the following Try block raises an error if the user does not enter a number. The Catch block catches this error: Try Dim sInput As String sInput = Inputbox("Enter a number.") If Not IsNumeric(sInput) Then Err.Raise(1) End If Catch When Err.Number = 1 Msgbox("Error1") End Try Note that code such as: Dim x As Integer Try x = 5 Catch When x = 5 MsgBox(x) End Try does not work (that is, the Catch statements are never executed) because no error was generated. The Exit Try statement is used to break out of any portion of a Try ... Catch ... Finally block. The optional finallyStatements code block is executed regardless of whether an error occurs (or is caught), unless an Exit Try statement is executed. This final code can be used for cleanup in the event of an error. (By placing an Exit Try at the end of the Try block, the finallyStatements are not executed if no error occurs.) As with unstructured error handling, VB may pass an error up the call stack when using structured error handling. This happens in the following situations:
9.2.2.2 Exception classesThe System namespace contains the Exception class, which is the base class for a substantial collection of derived exception classes, listed as follows . Note that the indentation indicates class inheritance. For example, EntryPointNotFoundException (the fifth from the last entry in the list) inherits from TypeLoadException. Exception ApplicationException SystemException AccessException FieldAccessException MethodAccessException MissingMemberException MissingFieldException MissingMethodException AppDomainUnloadedException AppDomainUnloadInProgressException ArgumentException ArgumentNullException ArgumentOutOfRangeException DuplicateWaitObjectException ArithmeticException DivideByZeroException NotFiniteNumberException OverflowException ArrayTypeMismatchException BadImageFormatException CannotUnloadAppDomainException ContextMarshalException CoreException ExecutionEngineException IndexOutOfRangeException StackOverflowException ExecutionEngineException FormatException InvalidCastException InvalidOperationException MulticastNotSupportedException NotImplementedException NotSupportedException PlatformNotSupportedException NullReferenceException OutOfMemoryException RankException ServicedComponentException TypeInitializationException TypeLoadException EntryPointNotFoundException TypeUnloadedException UnauthorizedAccessException WeakReferenceException URIFormatException As Microsoft states: "Most of the exception classes that inherit from Exception do not implement additional members or provide additional functionality." Thus, it is simply the class name that distinguishes one type of exception from another. The properties and methods applied to an exception object are inherited from the Exception base class. When writing Catch blocks, we always face the question of whether to simply trap the generic exception class, as in: Sub test( ) Try ... Catch e As Exception ... End Try End Sub or whether to trap specific exception classes. Of course, the time to trap specific exception classes is when we want to handle errors differently based on their class. For instance, this may take the form of issuing different custom error messages for different exception types. Also, there are occasions when we may want to take advantage of members of a particular exception class that are not implemented in the Exception base class. For instance, the ArgumentException class has a ParamName property that returns the name of the parameter that causes the exception. Now, if we simply trap the generic Exception class, as in the following code: Sub test( ) Try Dim s, d As String s = "c:\temp.txt" ' Attempt to copy a file to a nonvalid target FileCopy(s, d) Catch e As Exception MsgBox(e.Message) End Try End Sub then we cannot take advantage of the ParamName property. On the other hand, if we specifically trap the ArgumentException class, as in the following code: Sub test1( ) Try Dim s, d As String s = "c:\temp.txt" ' Attempt to copy a file to a nonvalid target FileCopy(s, d) Catch e As ArgumentException MsgBox(e.Message & " Parameter: " & e.ParamName) End Try End Sub then we can retrieve the name of the offending parameter. Now let us take a look at some of the members of the Exception class:
The best way to get a feel for these members is with an example. Consider the following code, which consists of three subroutines. The first subroutine, Exception0 , contains a Try ... Catch ... statement. In the Try code block, the subroutine Exception0 calls the subroutine Exception1 , which simply calls Exception2 . Sub Exception0( ) Dim s As String Try Exception1( ) Catch e As Exception s = "Message: " & e.Message s = s & ControlChars.CrLf & "Source: " & e.Source s = s & ControlChars.CrLf & "Stack: " & e.StackTrace s = s & ControlChars.CrLf & "Target: " & e.TargetSite.Name s = s & ControlChars.CrLf & "ToString: " & e.ToString debug.writeline(s) End Try End Sub Sub Exception1( ) Exception2( ) End Sub Sub Exception2( ) Throw New ArgumentNullException( ) End Sub In Exception2 , there is a single line of code that executes the Throw statement, which throws an exception. This is similar to raising an error with the Err.Raise method. However, as you can see by the New keyword, the Throw statement actually creates an object of one of the exception types. The output from the call to Exception0 is: Message: argument can't be null Source: Stack: at WindowsApplication3.Form1.Exception2( ) in C:\VBNET\Form1.vb:line 68 at WindowsApplication3.Form1.Exception1( ) in C:\VBNET\Form1.vb:line 66 at WindowsApplication3.Form1.Exception0( ) in C:\VBNET\Form1.vb:line 53 Target: Exception2 ToString: System.ArgumentNullException: argument can't be null at WindowsApplication3.Form1.Exception2( ) in C:\VBNET\Form1.vb:line 68 at WindowsApplication3.Form1.Exception1( ) in C:\VBNET\Form1.vb:line 66 at WindowsApplication3.Form1.Exception0( ) in C:\VBNET\Form1.vb:line 53 |