Chapter 15. Exceptions

Introduction

Visual Basic has included error handling since its initial release through the On Error statement. Although often derided by developers, this mechanism did effectively catch and process all errors when used properly. Visual Basic 2005 still includes this error-handling methodology, but it also includes structured error handling, new with .NET. This chapter considers this new error-processing system, comprised of the Try…Catch…Finally statement and System.Exception-derived error objects.

15.1. Catching an Exception

Problem

Although you’ve been a Visual Basic 6.0 developer for years, and you’ve already used On Error statements in your Visual Basic 2005 code, you want to try out the structured error-handling statements you’ve heard so much about.

Solution

Use the Try…Catch…Finally block statement to locally monitor and handle errors. The statement has three sections:

Try

The code you need to monitor for errors appears in this first section.

Catch

When an error occurs, processing jumps immediately from the Try section to a matching Catch block (We’ll define “matching” shortly). Any remaining unprocessed statements in the Try block are ignored. You can have any number of Catch entries in your error-handling block.

Finally

Any code you include in this optional section runs whether an exception occurs or not. It’s a useful place to put any cleanup code related to resources you allocated in the Try section.

Here’s the syntax of the Try…Catch…Finally statement:

	Try
	   ' ----- Error-prone code here.
	Catch ex As  
System.Exception
	   ' ----- Error-processing code here. Multiple
	   '       Catch blocks can be included.
	Finally
	   ' ----- Cleanup code here (optional).
	End Try

Discussion

Although Visual Basic 2005 still supports the On Error statement and related error-handling logic found way back in Visual Basic 1.0, it also includes a new “structured” error-handling system that more closely parallels the object-oriented nature of .NET. In this system, exceptions (errors) exist as objects, inherited from the System.Exception class. When an error occurs in your code, .NET wraps it up in a System.Exception object (or one of its more specific derived classes) and triggers it in your code. The Try…Catch statement watches for any such exceptions and jumps to a Catch block when an exception occurs.

System.Exception represents the most general type of exception; because all exception objects derive from it, it catches all error types. In this statement:

	Try
	   ' ----- Error-prone code here.
	Catch ex As System.Exception
	   ' ----- Error-processing code here.
	End Try

any type of error that occurs in the Try block, no matter what it is, falls into the Catch block, since that block catches every type of error.

.NET also defines more specific exceptions. For example, the System.OutOfMemoryException error occurs when any operation lacks sufficient memory to execute properly:

	Try
	   ' ----- Error-prone code here.
	Catch ex As System.OutOfMemoryException
	   ' ----- Handle memory errors here.
	Catch ex As System.Exception
	   ' ----- Handle all other errors here.
	End Try

Each Catch block handles only the error types specified in its As clause. In the above block of code, the first Catch block handles OutOfMemoryException errors. Any other error that occurs in the Try block skips over that first Catch entry and jumps into the second, more general Catch block. This is what is meant by a “matching” Catch block, as mentioned earlier in this recipe. Exceptions seek the first matching Catch clause, based on an exact class match or a derived match relationship.

When an error occurs, the generated exception is compared to each Catch block’s As clause for a match, in order from top to bottom. Therefore, you should place the most restrictive error type first, saving System.Exception for the last Catch block. If no error occurs, all Catch blocks are ignored.

Within a Catch block, the ex variable (included just after the Catch keyword) provides access to the actual exception object. Use its members as you would the members of any other object. A description of the exception appears as ex.Message. You can name the variable anything you want; the name ex has become common in technical documentation, but you are free to change it or even vary it between the different Catch clauses.

If included, the Finally block is always processed, no matter what. It is processed after the relevant Try and Catch blocks complete. Even if you issue an Exit Sub or similar statement from within a Try or Catch block, the Finally section is still processed. All Try statements must include at least one Catch or Finally block.

There are some restrictions on Try…Catch statements. In general, you cannot use GoTo statements to jump into or out of any of the blocks. There is an Exit Try statement that lets you jump out early, but it can’t be used in the Finally block.

If an error occurs in a routine but no error handling is in effect (i.e., the code is out-side of a Try statement, and no On Error statements appear in the procedure), the error “bubbles up” to the calling procedure, looking for another active error handler to deal with the exception. If no error handlers are available to deal with the error, a message is displayed to the user, and the application exits.

See Also

Recipe 15.3 discusses a global exception handler that Catches any exceptions not dealt with in local procedures.

15.2. Throwing an Exception

Problem

An invalid condition has occurred in your custom class code, and you want to generate an exception to inform the calling code of the problem.

Solution

Use the Throw statement to send an exception to the next available error handler. Throw takes an instance of a System.Exception (or derived) object as its only argument:

	Throw New System.Exception("A great big error occurred.")

You can also prepare your exception object in advance and then use its variable in the Throw statement:

	Dim errorDetail As New System.ArgumentOutOfRangeException( _
	   "Year", "The 'Year' must be at least 1995.")
	Throw errorDetail

Discussion

When .NET detects an error in your program, it also uses the Throw statement to send errors to your code. When you use the Throw statement, your generated errors look just like those issued by the Framework.

You can generate an error at any time using the Throw statement, even within a Try block. The related Catch handler will process the error as if some other system-defined process had generated the error.

Visual Basic also includes an Err.Raise method that generates errors, as was done using pre-.NET versions of Visual Basic. It focuses on error numbers rather than on object-based exceptions. Although .NET will wrap errors issued through Err.Raise in an Exception object, you should use this method only for backward compatibility. Use the Throw statement instead.

15.3. Catching Unhandled Exceptions

Problem

Although you make judicious use of Try…Catch and On Error statements in your code, it’s possible that some exceptions will sneak through your structured and unstructured error-handling barriers. You want to keep these errors from crashing the program.

Solution

Sample code folder: Chapter 15 UnhandledException

Handle the application-level UnhandledException event to capture any errors not dealt with elsewhere in your code. This global error handler is part of the Windows Forms Application Framework. In the Project Properties window’s Application panel, make sure that “Enable application framework” is selected, and then click on the View Application Events button on that same panel. Visual Studio opens the ApplicationEvents.vb source file, which looks like this:

	Namespace My
	   Partial Friend Class MyApplication

	   End Class
	End Namespace

The global error handler will appear in this MyApplication class. Select “(MyApplication Events)” from the Class Name list above and to the left of the code editor window, and then select “UnhandledException” from the Method Name list just to the right of that. Visual Studio will add a template for the UnhandledException event handler:

	Private Sub MyApplication_UnhandledException( _
	      ByVal sender As Object, ByVal e As Microsoft. _
	      VisualBasic.ApplicationServices. _
	      UnhandledExceptionEventArgs) _
	      Handles Me.UnhandledException
	End Sub

Code added to this event handler will run whenever an unhandled error or exception occurs somewhere in your application. Once you have dealt with the error, you can either exit the application immediately (in a more controlled manner than just letting the program crash) or return to a basic waiting-for-input-from-the-user state. Use the e argument’s ExitApplication property to indicate which choice you want to make. Setting this property to True, as shown here, will terminate the program:

	Private Sub MyApplication_UnhandledException( _
	      ByVal sender As Object, ByVal e As Microsoft. _
	      VisualBasic.ApplicationServices. _
	      UnhandledExceptionEventArgs) _
	      Handles Me.UnhandledException
	   MsgBox("An unhandled error occurred. That's bad.")
	   e.ExitApplication = True
	End Sub

This code is never called when your application runs in the debugger.

Discussion

The solution listed above is valid only for Windows Forms applications that use the Application Framework. If you choose to disable the Application Framework, or you are writing a non–Windows Forms application, you must manually establish a global error handler for each thread of your application. We’ll look at the first case here.

Create a new Windows Forms application, and clear the “Enable application frame-work” field in the Project Properties window. Open up the source code window for the Form1 form, and replace the basically empty content with the following code:

	Public Class Form1
	   Private Sub Form1_Click(ByVal sender As Object, _
	         ByVal e As System.EventArgs) Handles Me.Click
	      ' ----- Cause a fake unhandled error.
	      Throw New System.Exception( )
	   End Sub

	   Private Sub Form1_FormClosed(ByVal sender As Object, _
	         ByVal e As System.Windows.Forms.FormClosedEventArgs) _
	         Handles Me.FormClosed
	      ' ----- Disable the monitor before exiting.
	      RemoveGlobalErrorMonitor( )
	   End Sub

	   Private Sub Form1_Load(ByVal sender As System.Object, _
	         ByVal e As System.EventArgs) Handles MyBase.Load
	      ' ----- Enable error monitoring.
	      AddGlobalErrorMonitor( )
	   End Sub
	End Class

	Module Module1
	   Public Sub AddGlobalErrorMonitor( )
	      ' ----- Enable global error monitoring on this thread.
	       
AddHandler Application.ThreadException, _
	         AddressOf GlobalErrorMonitor
	   End Sub

	   Public Sub RemoveGlobalErrorMonitor( )
	      ' ----- Disable global error monitoring on this thread.
	      RemoveHandler Application.ThreadException, _
	         AddressOf GlobalErrorMonitor
	   End Sub

	   Public Sub GlobalErrorMonitor(ByVal sender As Object, _
	         ByVal e As System.Threading.ThreadExceptionEventArgs)
	      ' ----- An unhandled global error occurred in the thread.
	      MsgBox("A global error was caught.")
	   End Sub
	End Module

This code uses the AddHandler statement to connect the thread’s Application. ThreadException event to a custom event handler, GlobalErrorMonitor(). It’s added immediately when the (main) form is first loaded, and it remains until the form closes. Remember that this code will not work properly within Visual Studio. You must build the application and run it directly before your global exception handler can be used.

When writing console applications, monitor the System.appdomain.CurrentDomain. UnhandledException event instead of Application.ThreadException:

	AddHandler System.appdomain.CurrentDomain. _
	   UnhandledException, AddressOf GlobalErrorMonitor

15.4. Displaying Exception Information

Problem

An error has occurred, and you want to inform the user in a friendly manner.

Solution

The captured exception object includes all the details concerning the error, with some parts ready for user-friendly presentation. The simplest presentation option uses the exception’s ToString() method to generate information about the error.

The following code generates the error message in Figure 15-1 when run within Visual Studio:

	Try
	   Throw New System.Exception( )
	Catch ex As System.Exception
	   MsgBox(ex.ToString( ))
	End Try
A basic error message
Figure 15-1. A basic error message

Discussion

If you encounter an exception in a block of code where you know errors are likely, you can sometimes compensate for the error through alternate logic without ever informing the user of the problem. In those cases where you cannot continue normally because of the error, your program can inform the user of the situation.

Beyond the basic ToString() output, you can handcraft the details of the exception into a form that better communicates the problem to the user. The System.Exception object includes the following useful properties:

Data

Some errors use the collection exposed by this property to store additional details related to the error. The type of data stored depends on the code that generated the error. It is most often used in custom exceptions.

InnerException

If this exception is a byproduct of another, earlier exception, this property exposes that previous exception.

Message

This property provides a short yet friendly description of the exception.

Source

This property specifies the name of the application, class, or process ID that generated the error.

StackTrace

This text property provides a semihuman-readable listing of the stack trace—the set of called methods that led up to the method generating the error. This stack trace may include internal procedures from the .NET Framework, and its overall length may shock the user.

TargetSite

This property exposes a MethodBase object that fully describes the procedure in which the exception occurred. The properties of this object may or may not be useful in every case, especially when an application has been obfuscated.

Other exception objects further derived from System.Exception may include additional properties with more detailed information. By concatenating the various properties of the captured exception object, you should be able to effectively communicate the problem to the user or store the details in an error log for later analysis.

15.5. Creating New Exception Types

Problem

None of the exception objects supplied with .NET really meets the needs of the error you need to generate.

Solution

Build your own exception object by deriving a new class from System.Exception or another class already derived from it.

Discussion

The following class extends the standard Exception object by adding a place for a SQL statement used in a database query:

	Public Class ExceptionWithSQL
	   Inherits System.Exception

	   Public SQLStatement As String

	   Public Sub New(ByVal message As String, _
	         ByVal sqlText As String, _
	         ByVal innerException As System.Exception)
	      ' ----- Store the details of this exception.
	      MyBase.New(message, innerException)
	      SQLStatement = sqlText
	   End Sub
	End Class

Many business applications that interact with a database use a central procedure to process SQL statements in a consistent manner. While this procedure may have its own error handler, the calling code also wants to know when an error occurred with the SQL statement that it provided. The following ProcessSQL method represents just such a common procedure. If an error occurs in the supplied SQL statement, it uses the ExceptionWithSQL class to communicate the problem:

	Public Sub ProcessSQL(ByVal sqlText As String)
	   Try
	      ' ----- Add ADO.NET-specific code here.
	   Catch ex As System.Exception
	      ' ----- Convert this to a SQL error.
	      Throw New WindowsApplication1.ExceptionWithSQL( _
	         "A SQL error occurred.", sqlText, ex)
	      ' ----- The calling procedure will receive the
	      '       modified error.
	   End Try
	End Sub

Since the calling code may issue several different SQL statements within a common Try block, having the errant SQL statement in the exception object provides the additional information a programmer may need to locate the problem:

	Dim sqlText As String
	Try
	   sqlText = "DELETE FROM Table1 WHERE RecordType = 5"
	   ProcessSQL(sqlText)
	   sqlText = "DELETE FROM Table2 WHERE RecordType = 5"
	   ProcessSQL(sqlText)
	Catch ex As WindowsApplication1.ExceptionWithSQL
	   MsgBox("The following SQL statement caused an error:" & _
	      vbCrLf & ex.SQLStatement)
	End Try

You can also create a new ExceptionWithSQL object for any reason on your own and Throw it, even if no underlying database error occurred. With custom errors, the choice of when to use them is yours.

Before .NET, errors in Visual Basic were identified solely by a number, many defined for common use by Microsoft Windows. For instance, error number 7 represents the “Out-of-memory” error condition.

In .NET, all errors are defined by specific classes derived from System.Exception. For example, out-of-memory errors are thrown as instances of System.OutOfMemoryException. You can derive your own exceptions for use in your application code. You will often derive such custom errors directly from System.Exception, but if another derived exception class contains features you don’t want to rewrite from scratch, you can derive from that class instead.

The various .NET exceptions derived from System.Exception can also be used directly. For instance, you can throw a System.DivideByZeroException even if you don’t actually perform an invalid division, but your code has a zero-value denominator ready to use:

	Public Function CheckAndDivide(ByVal numerator As Decimal, _
	      ByVal denominator As Decimal) As Decimal
	   ' ----- Divide numbers, but check for divide-by-zero first.
	   If (denominator = 0@) Then
	      Throw New System.DivideByZeroException( )
	   Else
	      Return numerator / denominator
	   End If
	End Function

15.6. Ignoring Exceptions in a Block of Code

Problem

You have a block of code that might generate errors, but you don’t really care. You want the code to continue on with or without errors and to provide no error report to the user.

Solution

To ignore errors, use the On Error Resume Next statement, or use a Try statement with an empty Catch block.

Discussion

In Visual Basic, the traditional way to ignore errors in a section of code is to use the On Error Resume Next statement. The following code shows both ignored and pro-cessed error-handler sections:

	Public Sub DoSomething( )
	   On Error Resume Next
	   ' ----- Error handling is now disabled. You can do
	   '       dangerous things and no errors will occur. The
	   '       "Err" object will still be filled in with
	   '       error content when an error does occur, so you
	   '       can check that if you are concerned.

	   On Error GoTo ErrorHandler
	   ' ----- Error handling has been turned back on. All
	   '       errors will jump down to the labeled section.
	   Exit Sub

	ErrorHandler:
	   ' ----- Do something with the error here, then…
	   Resume Next
	End Sub

If you want to ignore errors but prefer using the structured exception-handling features, add a Try block with an empty Catch block:

	Public Sub DoSomething( )
	   Try
	      ' ----- As expected, any error that occurs here will
	      '       jump to the Catch block.
	   Catch
	      ' ----- If you don't include any error-handling code
	      '       here, the error is just ignored.
	   End Try

	   ' ----- Errors that occur out here will not be caught by
	   '       the Try block, but you knew that already.
	End Sub

There is a small difference between these two blocks of code. When using the On Error Resume Next statement, any error on a statement causes the code to continue with the next statement. In the Try…Catch example, any error that occurs in the Try block causes the code to continue with the Catch block, and then with the code that follows the entire Try…End Try section. This means that if you have multiple statements in the Try block and an error occurs on the first of those statements, the remaining statements in the Try block are skipped completely.

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

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