Structured-Exception-Handling Keywords


Structured exception handling depends on several new keywords in VB 2005:

  • 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. In some respects, this would be the equivalent of an On Error statement in VB6. However, unlike an On Error statement, a Try statement does not indicate where a trapped exception should be routed. Instead, the exception is automatically routed to a Catch statement (discussed next).

  • Catch - Begins an exception handler for a type of exception. One or more Catch code blocks follow a Try block, with each Catch block catching a different type of exception. When an exception is encountered in the Try block, the first Catch block that matches that type of exception receives control. A Catch statement is analogous to the line label used in a VB6 On Error statement, but the ability to route different types of exceptions to different Catch statements is a radical improvement over VB6.

  • Finally - 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. There is no equivalent of a Finally in VB6.

  • Throw - Generates an exception. This is similar to Err.Raise in VB6. It’s usually done in a Catch block when the exception should be kicked back to a calling routine or in a routine that has itself detected an error such as a bad argument passed in.

The Try, Catch, and Finally Keywords

Here is an example showing some typical simple structured exception handling code in VB 2005. In this case, the most likely source of an error is the iItems argument. If it has a value of zero, then this would lead to dividing by zero, which would generate an exception.

First, create a Windows Application in Visual Basic 2005 and place a button on the default Form1 created in the project. In the button’s click event, place the following two lines of code:

  Dim sngAvg As Single sngAvg = GetAverage(0, 100) 

Then put the following function in the form’s code:

  Private Function GetAverage(iItems As Integer, iTotal As Integer) as Single      ' Code that might throw an exception is wrapped in a Try block     Try         Dim sngAverage As Single         ' This will cause an exception to be thrown if iItems = 0         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excGeneric As Exception         ' If the calculation failed, you get here         MessageBox.Show("Calculation unsuccessful - exception caught")         Return 0      End Try End Function 

This code traps all the exceptions with a single generic exception type, and you don’t have any Finally logic. Run the program and press the button. You will be able to follow the sequence better if you place a breakpoint at the top of the GetAverage function and step through the lines.

Here is a more complex example that traps the divide-by-zero exception explicitly. This second version of the GetAverage function (notice that the name is GetAverage2) also includes a Finally block:

 Private Function GetAverage2(iItems As Integer, iTotal As Integer) as Single      ' Code that might throw an exception is wrapped in a Try block     Try         Dim sngAverage As Single         ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error.         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block         MessageBox.Show("Calculation generated DivideByZero Exception")         Return 0     Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")         Return 0     Finally         ' Code in the Finally block will always run.         MessageBox.Show("You always get here, with or without an error")     End Try End Function

This code contains two Catch blocks for different types of exceptions. If an exception is generated, then .NET will go down the Catch blocks looking for a matching exception type. That means the Catch blocks should be arranged with specific types first and more generic types after.

Place the code for GetAverage2 in the form, and place another button on Form1. In the Click event for the second button, place the following code:

  Dim sngAvg As Single sngAvg = GetAverage2(0, 100) 

Run the program again and press the second button. As before, it’s easier to follow if you set a breakpoint early in the code and then step through the code line by line.

The Throw Keyword

Sometimes a Catch block is unable to handle an error. Some exceptions are so unexpected that they should be “sent back up the line” to the calling code, so that the problem can be promoted to code that can decide what to do with it. A Throw statement is used for that purpose.

A Throw statement, like an Err.Raise, 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.

You can see the Throw statement in action by changing the earlier code for GetAverage2 to look like this:

 Private Function GetAverage3(iItems As Integer, iTotal as Integer) as Single      ' Code that might throw an exception is wrapped in a Try block     Try         Dim sngAverage As Single         ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error.         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block.         MessageBox.Show("Calculation generated DivideByZero Exception")          Throw excDivideByZero        MessageBox.Show("More logic after the throw  never executed")     Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")          Throw excGeneric     Finally         ' Code in the Finally block will always run, even if         ' an exception was thrown in a Catch block.         MessageBox.Show("You always get here, with or without an error")     End Try End Function

Here is some code to call GetAverage3. You can place this code in another button’s click event to test it out:

  Try     Dim sngAvg As Single     sngAvg = GetAverage3(0, 100) Catch exc As Exception     MessageBox.Show("Back in the click event after an error") finally     MessageBox.Show("Finally block in click event") End Try 

Throwing a New Exception

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, since 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. To show how this is done, let’s change the aforementioned example to throw your own exception instead of the one caught in the Catch block:

 Private Function GetAverage4(iItems As Integer, iTotal as Integer) as Single     If iItems = 0 Then         Dim excOurOwnException As New _              ArgumentException("Number of items cannot be zero")          Throw excOurOwnException     End If  ' Code that might throw an exception is wrapped in a Try block.     Try         Dim sngAverage As Single                  ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)                  ' This only executes if the line above generated no error.         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block.         MessageBox.Show("Calculation generated DivideByZero Exception")         Throw excDivideByZero         MessageBox.Show("More logic after the thrown - never executed")     Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")          Throw excGeneric     Finally         ' Code in the Finally block will always run, even if         ' an exception was thrown in a Catch block.         MessageBox.Show("You always get here, with or without an error")     End Try End Function

This code can be called from a button with similar code for calling GetAverage3. Just change the name of the function called to GetAverage4.

This technique is particularly well suited to dealing with problems detected in property procedures. Property Set procedures often do checking to ensure that the property is about to be assigned a valid value. If not, 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 are going to exit a Catch block if the value of iItems is 0, because you know that your error was caused by that problem:

 Private Function GetAverage5(iItems As Integer, iTotal as Integer) As Single      ' Code that might throw an exception is wrapped in a Try block.     Try         Dim sngAverage As Single         ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error.         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block.          If iItems = 0 Then              Return 0              Exit Try          Else              MessageBox.Show("Error not caused by iItems")          End If         Throw excDivideByZero         MessageBox.Show("More logic after the thrown - never executed")     Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")         Throw excGeneric     Finally         ' Code in the Finally block will always run, even if         ' an exception was thrown in a Catch block.         MessageBox.Show("You always get here, with or without an error")     End Try End Sub

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 by the value of intY being 0). The Exit Try goes immediately to the Finally block and completes the processing there:

 If iItems = 0 Then   Return 0   Exit Try Else   MessageBox.Show("Error not caused by iItems") End If

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

Nested Try Structures

In some cases, 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. You can alter the example under the section “The Throw Keyword” to demonstrate the following code:

 Private Function GetAverage6(iItems As Integer, iTotal as Integer) As Single     ' Code that might throw an exception is wrapped in a Try block.     Try         Dim sngAverage As Single         ' Do something for performance testing....         Try             LogEvent("GetAverage")         Catch exc As Exception             MessageBox.Show("Logging function unavailable")         End Try         ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error.         MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block.         MessageBox.Show("Error not divide by 0")         Throw excDivideByZero         MessageBox.Show("More logic after the thrown - never executed")      Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")         Throw excGeneric     Finally         ' Code in the Finally block will always run, even if         ' an exception was thrown in a Catch block.         MessageBox.Show("You always get here, with or without an error")     End Try End Function

In the preceding example, you are assuming that a function exists to log an event. This function would typically be in a common library, and might log the event in various ways. You will look at logging exceptions in detail later in the chapter, but a simple LogEvent function might look like this:

  Public Function LogEvent(ByVal sEvent As String)      FileOpen(1, "logfile.txt", OpenMode.Append)      Print(1, DateTime.Now & "-" & sEvent & vbCrLf)      FileClose(1) End Function 

In this case, you don’t want a problem logging an event, such as a “disk full” error, to crash the routine. The code for the GetAverage function triggers a message box to indicate trouble with the logging function.

A Catch block can be empty. In that case, it has a similar effect as On Error Resume Next in VB6. The exception is ignored. However, execution does not pick up with the line after the line that generated the error, but instead picks up with either the Finally block or the line after the End Try if no Finally block exists.

Using Exception Properties

The previous examples have displayed hard-coded messages into message boxes, and this is obviously not a good technique for production applications. Instead, a message box or log entry describing an exception should provide as much information as possible concerning the problem. To do this, various properties of the exception can be used.

The most brutal way to get information about an exception is to use the ToString method of the exception. Suppose that you modify the earlier example of GetAverage2 to change the displayed information about the exception like this:

 Private Function GetAverage2(ByVal iItems As Integer, ByVal iTotal As Integer) _         As Single     ' Code that might throw an exception is wrapped in a Try block.     Try         Dim sngAverage As Single         ' This will cause an exception to be thrown.         sngAverage = CSng(iTotal \ iItems)         ' This only executes if the line above generated no error.          MessageBox.Show("Calculation successful")         Return sngAverage     Catch excDivideByZero As DivideByZeroException         ' You'll get here with an DivideByZeroException in the Try block.          MessageBox.Show(excDivideByZero.ToString)         Throw excDivideByZero         MessageBox.Show("More logic after the thrown - never executed")     Catch excGeneric As Exception         ' You'll get here when any exception is thrown and not caught in         ' a previous Catch block.         MessageBox.Show("Calculation failed - generic exception caught")         Throw excGeneric     Finally         ' Code in the Finally block will always run, even if         ' an exception was thrown in a Catch block.         MessageBox.Show("You always get here, with or without an error")     End Try End Function

When the function is accessed with iItems = 0, a message box similar to the one in Figure 9-1 will be displayed.

image from book
Figure 9-1

The Message Property

The message in the dialog shown in Figure 9-1 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, and that is supplied by the Message property.

If the previous code is changed so that the Message property is used instead of ToString, then the message box will provide something like what is shown in Figure 9-2.

image from book
Figure 9-2

The InnerException and TargetSite Properties

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 your exceptions 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, you’ll start a new code sample, with just a subroutine that generates its own exception. You’ll include code to add a reference to an InnerException object to the exception you are generating with the Throw method.

This example also includes a message box to show what’s stored in the exception’s TargetSite property. As shown in the results, TargetSite will contain the name of the routine generating the exception - in this case, HandlerExample. Here’s the code:

    Sub HandlerExample()    Dim intX As Integer    Dim intY As Integer    Dim intZ As Integer    intY = 0    intX = 5    ' First Required Error Statement.    Try      ' Cause a "Divide by Zero"      intZ = CType((intX \ intY), Integer)    ' Catch the error.    Catch objA As System.DivideByZeroException       Try         Throw (New Exception("0 as divisor", objA))       Catch objB As Exception         Dim sError As String         sError = "My Message: " & objB.Message & vbCrLf & vbCrLf         sError &= "Inner Exception Message: " & _             objB.InnerException.Message & vbCrLf & vbCrLf         sError &= "Method Error Occurred: " & objB.TargetSite.Name         MessageBox.Show(sError)       End Try    Catch      Messagebox.Show("Caught any other errors")    Finally      Messagebox.Show(Str(intZ))    End Try End Sub 

As before, you catch the divide-by-zero error in the first Catch block, and the exception is stored in objA so that you can reference its properties later.

You throw a new exception with a more general message (“0 as divisor”) that is easier to interpret, and you build up your stack by appending objA as the InnerException object using an overloaded constructor for the Exception object:

 Throw (New Exception("0 as divisor", objA))

You catch your newly thrown exception in another Catch statement. Note how it does not catch a specific type of error:

 Catch objB As Exception

Then you construct an error message for the new exception and display it in a message box:

 Dim sError As String sError = "My Message: " & objB.Message & vbCrLf & vbCrLf sError &= "Inner Exception Message: " & _     objB.InnerException.Message & vbCrLf & vbCrLf sError &= "Method Error Occurred: " & objB.TargetSite.Name MessageBox.Show(sError)

The message box that is produced is shown in Figure 9-3.

image from book
Figure 9-3

First your own message is included, based on the new exception thrown by your own code. Then the InnerException gets the next exception in the stack, which is the divide-by-zero exception, and its message is included. Finally, the TargetSite property gives you the name of the method that threw the exception. TargetSite is particularly helpful in logs or error reports from users that are used by developers to track down unexpected problems.

After this message box, the Finally clause displays another message box that just displays the current value of intZ, which is zero because the divide failed. This second box also occurs in other examples that follow.

Source and StackTrace

The Source and StackTrace properties provide the user with information regarding where the error occurred. This supplemental information can be invaluable for the user to pass on to the troubleshooter in order to help resolve errors more quickly. The following example uses these two properties and shows the feedback when the error occurs:

 Sub HandlerExample2()  Dim intX As Integer  Dim intY As Integer  Dim intZ As Integer  intY = 0  intX = 5  ' First Required Error Statement.   Try     ' Cause a "Divide by Zero"     intZ = CType((intX \ intY), Integer)   ' Catch the error.   Catch objA As System.DivideByZeroException      objA.Source = "HandlerExample2"      Messagebox.Show("Error Occurred at :" & _          objA.Source & objA.StackTrace)   Finally      Messagebox.Show(Str(intZ))   End Try End Sub

The output from the Messagebox statement is very detailed, providing the entire path and line number where the error occurred, as shown in Figure 9-4.

image from book
Figure 9-4

Notice that this information is also included in the ToString method examined earlier (refer to Figure 9-1).

GetBaseException

The GetBaseException method comes in very handy when you are deep in a set of thrown exceptions. This method returns the originating exception, which makes debugging easier and helps keep the troubleshooting process on track by sorting through information that can be misleading:

 Sub HandlerExample3()   Dim intX As Integer   Dim intY As Integer   Dim intZ As Integer   intY = 0   intX = 5   ' First Required Error Statement.   Try     ' Cause a "Divide by Zero"     intZ = CType((intX \ intY), Integer)   ' Catch the error.   Catch objA As System.DivideByZeroException      Try        Throw (New Exception("0 as divisor", objA))      Catch objB As Exception        Try          Throw (New Exception("New error", objB))        Catch objC As Exception          Messagebox.Show(objC.GetBaseException.Message)        End Try      End Try   Finally     Messagebox.Show(Str(intZ))   End Try End Sub

The InnerException property provides the information that the GetBaseException method needs, so as your example executes the Throw statements, it sets up the InnerException property. The purpose of the GetBaseException method is to provide the properties of the initial exception in the chain that was produced. Hence, objC.GetBaseException.Message returns the Message property of the original OverflowException message even though you’ve thrown multiple errors since the original error occurred:

 Messagebox.Show(objC.GetBaseException.Message)

To put it another way, the code traverses back to the exception caught as objA and displays the same message as the objA.Message property would, as shown in Figure 9-5.

image from book
Figure 9-5

HelpLink

The HelpLink property gets or sets the help link for a specific Exception object. It can be set to any string value, but is typically set to a URL. If you create your own exception in code, you might want to set HelpLink to some URL 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 exc As New ApplicationException("A short description of the problem") exc.HelpLink = "http://mysite.com/somehtmlfile.htm" Throw exc 

When trapping an exception, the HelpLink can be used to launch a viewer so the user can see the details about the problem. The following example shows this in action, using the built-in Explorer in Windows:

 Sub HandlerExample4() Try     Dim exc As New ApplicationException("A short description of the problem")     exc.HelpLink = "http://mysite.com/somehtmlfile.htm"     Throw exc     ' Catch the error. Catch objA As System.Exception     Shell("explorer.exe " & objA.HelpLink) End Try End Sub

This results in launching Internet Explorer to show the page specified by the URL. Most exceptions thrown by the CLR or the .NET Framework’s classes have a blank HelpLink property. You should only count on using HelpLink if you have previously set it to a URL (or some other type of link information) yourself.




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

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