Handling Exceptions
One of the most fundamental and often most complex tasks of a developer is to design exception-handling procedures in order to allow an application to recover gracefully from an unexpected or disallowed condition.
The .NET Framework allows exception handling to be performed between languages, across multiple computers, and even through integration with COM and other legacy solutions. Within Visual Basic .NET, the most common methods of exception handling involve the Try, Catch, and Finally blocks in addition to the Throw statement.
TIP
The Try, Catch, and Finally blocks must be consecutive within code. You may not have intervening code separating these blocks when configuring event-handling solutions within your code. Everything between the Try and End Try keywords is part of a single error-handling unit.
The Try Block
When code is processed that may generate an exception, the code may be placed within a Try block, which must in turn be followed by one or more Catch blocks or a Finally block. Exceptions raised cause the CLR to search for the nearest Try block (which may be nested within one another) and then pass control to the following Catch block or on to the Finally block if no Catch block is present in the code.
A Try block in its most basic form looks like this:
Try ' Code that may cause an exception Catch ' Code that handles any exception End Try
Any normal code may be placed within the Try block, including another Try block or calls to methods including Try blocks of their own. When an exception occurs, the CLR will find the nearest Try block and pass control to the following associated exception-handling blocks.
CAUTION
A Try block must be followed by one or more Catch blocks or by a Finally block.
The Catch Block
After an exception is encountered within a Try block, control is passed to the following Catch blocks or to the Finally block. Catch blocks are evaluated in order until a match to the exception type is made. A particular exception might be matched by many different Catch blocks of varying levels of generality.
For example, a DivideByZeroException might match a Catch block for DivideByZeroException, ArithmeticException, SystemException, or Exception. These are all increasingly general exception classes that contain the specific DivideByZeroException. If no matching Catch block can be found, the exception is passed back to the code that raised the exception and is considered an unhandled exception, which is discussed in greater detail later in this chapter.
NOTE
The default behavior for an unhandled exception is to cause the program to terminate with an error message.
TIP
Because Catch blocks are evaluated in the order that they occur in your code, you must always place the most specific exceptions first if you have multiple Catch blocks. Otherwise, the code will not compile.
A Try block followed by some Catch exception blocks might look like this:
Private Sub btnCalculate_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCalculate.Click Try Dim decMiles As Decimal = Convert.ToDecimal(txtMiles.Text) Dim decGallons As Decimal = Convert.ToDecimal(txtGallons.Text) Dim decEfficiency As Decimal = decMiles / decGallons txtEfficiency.Text = String.Format("{0:n}", decEfficiency) ' each try block should at least have one catch or finally block ' catch blocks should be in order of specific to the generalized ' exceptions. Otherwise compilation generates an error Catch fe As FormatException Dim msg As String = String.Format("Message: {0}" & _ vbcrlf & "Stack Trace: " & vbcrlf & " {1}", _ fe.Message, fe.StackTrace) MessageBox.Show(msg, fe.GetType().ToString()) Catch dbze As DivideByZeroException Dim msg As String = String.Format("Message: {0}" & _ vbcrlf & " Stack Trace: " & vbcrlf & " {1}", _ dbze.Message, dbze.StackTrace) MessageBox.Show(msg, dbze.GetType().ToString()) ' catches all CLS-compliant exceptions Catch ex As Exception Dim msg As String = String.Format("Message: {0}" & _ vbcrlf & " Stack Trace: " & vbcrlf & " {1}", _ ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) ' catches all other exceptions including ' non-CLS-compliant exceptions Catch ' just rethrow the exception to the caller Throw End Try End Sub
Here, an exception raised within the code is thrown to the Catch blocks in order to see if a match is made. The inclusion of the Catch block that uses the general Exception class will prevent an unhandled exception. When this code is compiled and run, an input zero value (DivideByZeroException), a nonnumeric value (FormatException), or a very large numeric value (OverflowException, caught by the Catch block as an exception) will not cause the program to terminate. A message will be returned to the user and the application will continue to function.
All languages that follow the Common Language Specification (CLS) will generate an exception that derives from System.Exception. The final Catch block in the code example (which does not specify an exception type at all) is included to handle exceptions generated by possible nonCLS-compliant languages. An unspecified Catch block is the most general form of Catch possible and should always be the last Catch block if multiple Catch blocks are present, because the first matching Catch block will be the one performed.
The Finally Block
The Finally block includes code that will run regardless of whether an exception is raised. This is a good location for cleanup code that will close open files or disconnect database connections to release held resources.
The following is an example of code that includes a Finally block:
Private Sub btnSave_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSave.Click ' A StreamWriter writes characters to a stream Dim sw As StreamWriter Try sw = New StreamWriter(txtFilename.Text) ' Attempt to write the textbox contents in a file Dim strLine As String For Each strLine In txtText.Lines sw.WriteLine(strLine) Next ' This line only executes if there were no exceptions so far MessageBox.Show("Contents written, without any exceptions") ' Catches all CLS-compliant exceptions Catch ex As Exception Dim msg As String = String.Format( _ "Message: {0}" & vbcrlf & " Stack Trace: " & vbcrlf & _ " {1}", ex.Message, ex.StackTrace) MessageBox.Show(msg, ex.GetType().ToString()) GoTo endit ' The finally block is always executed to make sure that the ' resources get closed whether or not an exception occurs. ' Even if there is a goto statement in a catch or try block the ' finally block is first executed before the control goes to the label Finally If Not sw Is Nothing Then sw.Close() End If MessageBox.Show( _ "Finally block always executes whether or not exception occurs") End Try EndIt: MessageBox.Show("Control is at label: end") End Sub
The Finally block may also be used in conjunction with a Try block, without any Catch blocks. Exceptions raised would be returned as unhandled exceptions in this case. An example of the simplest form of this follows:
Try ' Write code to allocate some resources Finally ' Write code to Dispose all allocated resources End Try
The Throw Statement
The Throw statement can be used to explicitly throw an exception within your code. It can be used in order to re-throw a caught exception to perform a task such as generating an Event Log entry or sending an email notification of the exception, or it may be used in order to raise a custom exception explicitly defined within your code.
NOTE
Exceptions should only be handled or explicitly thrown when doing so improves application usability and robustness. Evaluation of exception-handling code adds to the resource requirements needed to run your application and can rapidly become "too much of a good thing."
Here's an example of a Throw statement in its simplest form being used to re-throw a caught exception:
Catch ex As Exception ' TODO: Add code to write to the Event Log Throw
To use the Throw statement with a custom error message, you may create something like this:
Dim strMessage As String = "EndDate should be greater than the StartDate" Dim exNew As ArgumentOutOfRangeException = _ New ArgumentOutOfRangeException(strMessage) Throw exNew
NOTE
The .NET Framework CLR includes many standard exception types, but on occasion it is necessary or desirable to create a custom exception to address a specific need within your code. This should be done only if there is not already an existing exception class that satisfies your requirements. Any new custom exception should use the System.ApplicationException class as its base class.