|only for RuBoard|
Visual Basic .NET supports two different systems for handling errors: structured and unstructured exception handling. This chapter focuses primarily on the structured approach. Unstructured error handling is a throwback to earlier versions of the language, while the structured approach is far more elegant. Structured exception handling was specifically designed to be a part of the language from the beginning, instead of tacked on as an addendum.
Example 6-1 shows the typical form of a structured exception handler. The Try...End Try block is somewhat misleading because it is not actually a single block; it is composed of several different blocks: a Try block, one or more Catch blocks, and a Finally block. A variable declared in a Try block is not available from a corresponding Catch or Finally blockeach has its own scope. The term "structured" reflects the block-like nature of the exception handler; it's structured much like the rest of VB.NET, hence the term .
Try 'Code where error can occur Catch SpecificException [When condition] 'Code to execute when exception is caught Catch NotSoSpecificException [When condition] 'Code to execute when exception is caught Catch GeneralException [When condition] 'Code to execute when exception is caught Finally 'Code to execute before leaving Try block End Try
The Try block contains code in which a possible error can occur. If the unthinkable happens, VB.NET looks for a Catch block that is capable of dealing with the exception, and control is transferred there. If an appropriate handler is not found, the call stack is searched until a suitable Catch block is found. Therefore, it is important that Catch blocks be ordered from the most specific to the least specific exception. For example, a structured error handler for file open operation places the FileNotFoundException first, followed by the FileLoadException , and then the general Exception handler. Additionally, each Catch block can specify a filter expression that determines whether the exception should be handled. This is done by placing a When clause after the exception and defining a Boolean condition, such as:
Catch Exception When (x > 4 And y < 10)
Regardless of whether or not an exception occurs, the code in the Finally block executes.
Note that the Finally clause always executes before the exception handler goes out of scope. If an exception occurs and a Catch block is not found in the local scope, the Finally block is executed before the call stack is traversed. Example 6-2 demonstrates this idea and shows how an exception is thrown by using the Throw statement.
Imports System Public Class Jump Public Sub Go( ) Try Console.WriteLine("Jump: Try") Throw New InvalidOperationException("Simulated Error") Catch e As ArgumentException Console.WriteLine("Jump: Catch") Finally Console.WriteLine("Jump: Finally") End Try End Sub End Class Friend Class Test Public Shared Sub Main( ) Try Console.WriteLine("Main: Try") Dim j As New Jump( ) j.Go( ) Catch e As InvalidOperationException Console.WriteLine("Main: Catch") Finally Console.WriteLine("Main: Finally") End Try End Sub End Class
When Jump.Go is called, a deliberate InvalidOperationException is thrown. The Try block in Go does not contain a Catch block that is capable of handling the exception (it only handles exceptions of type ArgumentException ), but the Try block in Main does. If you examine the output of this program, you will see the following code:
C:\>try Main: Try Jump: Try Jump: Finally Main: Catch Main: Finally
After the InvalidOperationException is thrown, control is transferred to the Finally clause in Jump before the exception is caught in Main .
If a Catch block handler is not found, the exception is considered unhandled and the runtime shuts the application down. This is the only situation in which a Finally block will not execute.
Using a Try block is fundamentally similar to the following unstructured error handling technique:
Public Sub Foo( ) On Error Goto errHandler 'Do work here Exit Sub errHandler: 'Handle error and recover here End Sub
As shown in Figure 6-1, all exceptions are ultimately derived from System.Exception . When you create your own exceptions, though, you should never derive directly from this class. Runtime exceptions used by the CLR are derived from System.SystemException , and user -defined exceptions are derived from System.ApplicationException .
Whenever possible, you should try to use an exception that is already defined in the .NET Framework. If invalid parameters are passed to a method, throw an ArgumentException . If an object is not in a valid state when a method is called, throw an InvalidOperationException .
Scanning namespace documentation for classes ending with "Exception" is a good way to learn about the various exceptions provided by the framework. If a method in the .NET Framework can throw an exception, the exception's type is usually listed in a table after the parameters and return values in the documentation. Eventually, you will become familiar with the available exceptions.
You should create a new exception class only when there is a programmatic benefit to doing so. Usually, derived classes contain new members or extend the base class in some way; with exceptions, this is not always the case. Many exception classes in .NET are member-for-member, exact copies of the classes they are derived from. The only difference is the name of the exception class itself. The kind of information provided is generally the same from one exception class to another. It is the type itself that allows the programmer to write granular exception handling code. Remember, deriving new exceptions does not necessarily involve extensibility at the source code level.
Like inheritance, exceptions are easy to overuse. A neophyte programmer tends to use them everywhere because they are convenient and easy to use, even if the code is harder to read or a client has to code around a method that throws 15 different "exceptions."
When you consider the use of exceptions, keep the following statement in mind: exceptions are used to handle exceptional conditions. They are exceptions, not the rule.
You can think of exceptional conditions as things that can't ever happen, but do anyway (every once in a while). On a properly configured system, a table in an average database application exists 99.99% of the time. The code that goes against that table "always" executes perfectly .
But what happens if the system is not properly configured? Configuration problems are usually sorted out before a system is deployed. However, what if someone accidentally deletes the table? What if the server on which the database resides goes down? These external situations can happen outside of an application's controlthey are exceptional situations. The normal use of a class should not result in any exceptions being thrown.
Let's examine a specific case in the .NET Framework in which an exception would have been inappropriate because the condition it would have indicated is not exceptional. String.IndexOf returns the location of one or more characters within the current instance of a String object. If the character or substring cannot be found, the method returns -1 . This behavior is not exceptionalit's expected. It would not be appropriate for this method to throw an exception when a substring could not be found.
On the other hand, the following code fragment does indicate exceptional behavior. Here, an oversight results in a substring search on an uninitialized string:
Dim address As String address.IndexOf("Don't Mess with Texas")
This code will compile because address is an actual String reference. When it is run, though, a System.NullReferenceException is thrown because the reference doesn't refer to anything.
However, don't underuse exception handling by writing methods that return error codes. The problem with using a return code to report an error is that you don't have to examine it. The discipline of handling errors in this situation rests on your shoulders alone. Exceptions force you to deal with an error or at least acknowledge that it occurred (an empty Catch block is one way to ignore an exception, as is a When clause that ignores errors under certain conditions). You can't just pretend that a thrown exception does not exist. If left unhandled, it eventually bubbles to the top of your application, forcing it to shut down. Consider Example 6-3, which does just that.
Imports System Public Class BadClass Public Sub BadMethod( ) Throw New ArgumentException("Bad things happened") End Sub End Class Friend Class Test Public Shared Sub Main( ) Dim b As New BadClass( ) b.BadMethod( ) End Sub End Class
Ignoring a return code can cause your program to fail unpredictably. In the worst case, it could cause your application to crash. Not so with exceptions. If left alone, an exception bubbles up the call stack and eventually reaches the top, where it causes your program to shut down. When the program does shut down, you get a stack trace showing exactly what type of exception was thrown, what file it occurred in, the class and method, and the line number where it occurred:
Unhandled Exception: System.ArgumentException: SimulatedError at BadClass.BadMethod( ) in C:\bad.vb:line 5 at Test.Main( ) in C:\bad.vb:line 12
|only for RuBoard|