You may want to raise errors out of your procedures to indicate to callers that some exception has occurred. You might want to simply pass back a standard runtime exception provided by the .NET Framework, or you might want to create your own exception condition. In either case, you'll use the Throw keyword to raise the exception out of the current block.
The Throw keyword works in much the same manner as the Err.Raise method in VB6.
You can determine which exceptions you want to handle and which ones you want to raise back to your callers. When an exception occurs, your options include the following:
Do nothing at all. In this case, the .NET runtime will automatically raise the exception back out to the procedure that called your code.
Catch specific errors. In this case, exceptions you do handle won't be passed back out, but those you don't handle will be thrown back to the calling procedure.
Handle all errors. Add a Catch e as Exception block to your set of Catch blocks, and no error will ever pass through your exception handling, unless you specifically throw an error yourself.
Throw errors. You have the option to throw any error back out to the caller, explicitly. Using the Throw statement, you can raise the current error, or any other error, to the caller's exception handler.
It might not be obvious why you would want to throw an error yourself. There are times, however, when you might want to accept any type of error, but present it to the caller of your procedure as a more generic error. Perhaps you want to "hide" the details of the error and simply indicate that some disk error occurred, for example, when the user attempts to open a file. In that case, no matter which exception has occurred, you might raise a generic disk-handling exception. You'll see an example of this in the next section.
Using the Throw Keyword
You can use the Throw keyword in two ways:
You can rethrow an error from within a Catch block:
Catch exp As Exception Throw
You can throw an error from within any code, including a Try block:
Throw New FileNotFoundException()
The first technique, rethrowing an exception, only works from within a Catch block. The second technique, throwing a new error, works anywhere.
Searching for Handlers
When you throw an exception, the .NET runtime works its way up the procedure call stack, looking for an appropriate exception handler. (If you're in a Try block when you throw your exception, the runtime will use the local Catch blocks, if any, to handle the exception first.) As soon as the runtime finds a Catch block for the exception you've thrown, it executes the code it finds there. If it can't find any appropriate Catch block all the way up the call stack, the runtime handles the exception itself (as shown earlier in Figure 12.2).
If you throw an exception using the Throw keyword, VB6-style On Error Goto error handling can trap the error as well. That is, the .NET runtime uses the same plumbing for all exceptions, whether you use the old or new error-handling conventions.
Passing Error Information
If you want to intercept different exceptions and raise them all back out to the caller as a single exception type, Throw makes it easy. In the next example, the code catches all exceptions, and regardless of what caused these exceptions, it throws a FileNotFoundException object back to the caller. In some cases, like this one, the calling procedure may not care exactly what happened or why the file couldn't be found. The caller may only care that the file wasn't available and needs to discern that particular exception from other, different exceptions.
The Exception object's constructor is overloaded in several ways. You can pass in no parameters (you'll get a generic Exception object, with default values for its properties), a string indicating the error message you want sent back to the caller, or a string and an Exception object indicating the error message and the original exception that occurred (filling in the InnerException property of the exception you pass back to the caller). The example here uses the final constructor, passing back the inner exception.
You may also wish to make the original exception information available to the caller, in addition to the exception your code raises. In that case, you'll find that the constructor for the Exception class provides an overloaded version that allows you to specify the inner exception. That is, you can pass the Exception object that originally raised the error. The caller can investigate this exception, if it needs to.
The InnerException property of an exception is itself an Exception object, and it may also have an InnerException property that isn't Nothing. Therefore, you may end up following a linked list of exceptions when you start digging into the InnerException property. You may need to continue retrieving the InnerException property repeatedly until the property returns Nothing, in order to dig through all the errors that may have occurred.
In the following example, the TestThrow procedure throws a FileNotFoundException back to its caller, no matter what error it receives. In addition, it fills in the exception's InnerException property with the original Exception object. The example in Listing 12.6 displays the fabricated error message, along with the text associated with the original exception:
Listing 12.6 You Can Throw an Exception, Wrapping Up the Original Exception in the InnerException Property
Private Sub ThrowException() Dim lngSize As Long Dim s As FileStream ' Catch an exception thrown by the called procedure. Try TestThrow() Catch exp As FileNotFoundException lblError.Text = "Error occurred: " & exp.Message ' Use exp.InnerException to get to error ' that triggered this one. Try lblError.Text = exp.InnerException.Message Catch ' Do nothing at all! End Try Catch exp As Exception End Try End Sub Private Sub TestThrow() Dim lngSize As Long Dim s As FileStream ' No matter what happens, throw back ' a File Not Found exception. Try s = File.Open(txtFileName.Text, FileMode.Open) lngSize = s.Length s.Close() Catch exp As Exception Throw (New FileNotFoundException( _ "Unable to open the specified file." exp)) End Try End Sub
Running Code Unconditionally
You may find that, in addition to code in the Try and Catch blocks, you want to add code that runs whether or not an error occurs. You may need to release resources, close files, or handle other issues that need to take place under any circumstances. In order to run code unconditionally, you'll need to use the Finally block.
To run code unconditionally, add a Finally block after any Catch blocks. The code in this block will run even if your code throws an exception, and even if you add an explicit Exit Function (or Exit Sub) statement within a Catch block.
You may decide, for example, that your code needs to set the FileStream object variable to Nothing, whether or not any error occurs when working with the file. You can modify the procedure to look like Listing 12.7, calling the finalization code regardless of whether an error occurs.
Listing 12.7 Use the Finally Block to Run Code Unconditionally
Private Function TestFinally() As Integer Dim lngSize As Long Dim s As FileStream ' Use Finally to run code no matter what else happens. ' A somewhat meaningless example, but you get the ' idea. Try s = File.Open(txtFileName.Text, FileMode.Open) lngSize = s.Length s.Close() Catch exp As Exception lblError.Text = exp.Message Finally ' Run this code no matter what happens. s = Nothing End Try End Function
Although your Try/End Try block must contain either one or more Catch blocks or a Finally block, it needn't contain both. That is, a Finally block without Catch blocks is fine. Why include a Finally block if you don't include a Catch block? If an exception occurs within your procedure, the .NET runtime will look for an appropriate exception handler, and that may mean it leaves your procedure (if there's no Catch block, this will certainly happen), looking up the call stack for that exception handler. If you want to run code before the runtime leaves your procedure, you need to include a Finally block.