Introduction to Exception Handling in Visual Basic .NET
Visual Studio Team
Knowledge rests not upon truth alone, but upon error also. — Carl Jung
Summary: This article provides an overview of structured and unstructured exception handling in Visual Basic .NET. It includes considerations that help you choose the right exception-handling alternative, the approaches involved in each alternative, how to create your own exceptions, and the exception object's properties. A table at the end lists the predefined exception classes and their derived classes. (17 printed pages)
Only perfect programmers create perfect code from the beginning. The rest must address imperfections along the way to developing a successful application.
Luckily for us Microsoft® Visual Basic® .NET offers two ways of handling exceptions. The first, unstructured, follows the exception-handling conventions of earlier versions of Visual Basic. The second, structured, handles exceptions in ways that resemble exception handling in Microsoft® Visual C#™ and Microsoft® Visual C++®.
This article, which is aimed at the beginning Visual Basic developer or at developers who are transitioning from earlier versions of Visual Basic to Visual Basic .NET, provides an overview of structured and unstructured exception handling. It includes considerations that help you choose the right exception-handling alternative, the approaches involved in each alternative, how to create your own exceptions, and the exception object's properties. By the time you are finished, you should understand how and when to incorporate exception handling in your code.
Definitions of Errors and Exceptions
The terms, error and exception, are often used interchangeably. In fact, an error, which is an event that happens during the execution of code, interrupts or disrupts the code's normal flow and creates an exception object. When an error interrupts the flow, the program tries to find an exception handler — a block of code that tells it how to react — that will help it resume the flow. In other words, an error is the event; an exception is the object that the event creates.
Programmers use the phrase "throwing an exception" to mean that the method in question encountered an error and reacted by creating an exception object that contains information about the error and when/where it occurred. Factors that cause errors and subsequent exceptions include user error, resource failures, and failures of programming logic. Such errors are related to how the code undertakes a specific task; they are not related to the purpose of the task.
For the purpose of this article, "exception handling" means interpreting and reacting to the exceptions created by errors.
Structured versus Unstructured — When to Use Which
Structured exception handling is simply that — using a control structure containing exceptions, isolated blocks of code, and filters to create an exception handling mechanism. This allows your code to differentiate between different types of errors and react in accordance with circumstances. In unstructured exception handling, an On Error statement at the beginning of the code handles all exceptions.
Structured exception handling is significantly more versatile, robust, and flexible than unstructured. If possible, use structured exception handling. However, you might use unstructured exception handling under these circumstances:
- You are upgrading an application written in an earlier version of Visual Basic.
- You are developing a preliminary or draft version of an application and you don't mind if the program fails to shut down gracefully.
- You know in advance exactly what will cause the exception.
- A deadline is pressing and you need to take shortcuts.
- Code is trivial or so short that you only need to test the branch of code generating the exception.
- You need to use the Resume Next statement, which is not supported in structured exception handling.
You cannot combine structured and unstructured exception handling in the same function. If you use an On Error statement, you cannot use a Try...Catch statement in the same function.
Regardless of which you choose to handle exceptions within your code, you must take a step back and examine what assumptions that code makes. For example, when your application asks the user to input a telephone number, the following assumptions come into play:
- The user will input a number rather than characters.
- The number will have a certain format.
- The user will not input a null string.
- The user has a single telephone number.
User input might violate any or all of these assumptions. Robust code requires adequate exception handling, which allows your application to recover gracefully from such a violation.
Unless you can guarantee that a method will never throw an exception under any circumstances, allow for informative exception handling. Exception handling should be meaningful. Beyond stating that something went wrong, messages resulting from exception handling should indicate why and where it went wrong. An uninformative message along the lines of "An error has occurred" only frustrates the user.
Structured exception handling tests specific pieces of the code and, as exceptions occur, adapts your exception-handling code to the circumstances that caused the exception. It is significantly faster in large applications than unstructured exception handling and allows more flexible response to errors as well as greater application reliability.
The Try...Catch...Finally control structure is fundamental to structured exception handling. It tests a piece of code, filters exceptions created by the execution of that code, and reacts differently based on the type of thrown exception.
The Try...Catch...Finally block
Try...Catch...Finally control structures test a piece of code and direct how the application should handle various categories of error. Each of the structure's three constituent parts plays a specific role in this process.
- The Try statement provides the code that is being tested for exceptions.
- Catch clauses identify blocks of code that are associated with specific exceptions. A Catch When block directs the code to execute under specific circumstances. A Catch without a When clause reacts to any exception. Therefore, your code might hold a series of specific Catch...When statements, each reacting to a specific type of exception, followed by a general Catch block that reacts to any exceptions that have not been intercepted by the preceding Catch...When clauses.
- The Finally statement contains code that executes regardless of whether or not an exception occurs within the Try block. A Finally statement will execute even after an Exit Try or Exit Sub. This code often performs clean-up tasks, such as closing files or clearing buffers.
What a Catch Clause Does
A Catch clause can take three possible forms: Catch, Catch...As, and Catch...When.
A Catch clause with no When keyword allows the associated statement block to handle any exception. Catch...As and Catch...When clauses catch a specific exception and allow the associated statement block to tell the application what to do. Catch...As and Catch...When clauses can also be combined in a single statement, such as
Catch ex As Exception When intResult <> 0.
If the exception is a result of resource failure, it should identify the resource and, if possible, provide troubleshooting advice or workaround tips. If the exception is a result of a failure of programming logic, the clause should, in all probability, allow the application to exit as gracefully as possible. If user error has caused the exception, however, the code should allow the user to correct his or her error and proceed.
Catch clauses are checked in the order in which they appear in the code. Therefore, catch clauses should move from the specific to the general as they progress through the sequence of code. Check a type before checking its base type, for example. A catch block handling System.Exception should only appear as a final block after the other possibilities have been exhausted.
Imports System Try varAvailableSeats = varAuditoriumSeats - varNumberOfGuests Catch ex As Exception When varAuditoriumSeats = 0 MsgBox("Auditorium lacks chairs!") Exit Sub Catch ex As Exception When varAvailableSeats < 0 MsgBox("There are no more available seats.") Exit Sub Finally MsgBox("Thank you for your interest in our concert.") End Try
The Exception Object
The Exception object provides information about any encountered exception. Whenever an exception is thrown, the properties of the Err object are set, and a new instance of the Exception object is created. Examine its properties to determine the code location, type, and cause of the exception.
Following are some useful properties of the Exception object:
- The HelpLink property can hold an URL that points the user to further information about the exception.
- The HResult property gets or sets HRESULT, a numerical value assigned to the exception. HRESULT is a 32-bit value that contains three fields: a severity code, a facility code, and an error code. The severity code indicates whether the return value represents information, a warning, or an error. The facility code identifies the area of the system responsible for the exception. The error code is a unique number assigned to represent the error.
- The InnerException property returns an exception object representing an exception that was already in the process of being handled when the current exception was thrown. The code handling the outer exception may be able to use the information from the inner exception in order to handle the outer expression with greater precision.
- The Message property holds a string, which is the text message that informs the user of the nature of the error and the best way or ways to address it. During the creation of an exception object, you can provide the string best suited to that particular exception. If none is provided, the default string will be provided and formatted according to the current culture.
- The Source property gets or sets a string containing the name of the object throwing the exception or the name of the assembly where the exception occurred.
- The StackTrace property holds a stack trace, which you can use to determine where in the code the error occurred. StackTrace lists all the called methods that preceded the exception and the line numbers in the source where the calls were made.
- The TargetSite property gets the method name that threw the current exception. If the name is not available and the stack trace is not Nothing, the TargetSite property obtains the method name from the stack trace.
Creating Your Own Exceptions for Structured Exception Handling
There are two defined subclasses of exceptions in the Exception base class: System.Exception and Application.Exception.
System.Exception is the class from which the .NET Framework derives the pre-defined common language runtime exception classes. It is thrown by the common language runtime when nonfatal errors occur. System.Exception does not provide information about the cause of the exception.
Note For further information about the predefined common language runtime exception classes, see Table 1 at the end of this article, which lists the predefined exception classes, their causes, and their derived classes.
You can create your own application exception classes by inheriting them from the Application.Exception class. Follow the strictures of good coding practice by ending the class name of your exception with the word "Exception" — for example,
The following example defines an exception class and defines three constructors for it, each of which takes different parameters.
Imports System Public Class GardenException Inherits System.ApplicationException Public Sub New() End Sub ' Creates a Sub New for the exception that allows you to set the ' message property when thrown. Public Sub New(Message As String) MyBase.New(Message) End Sub ' Creates a Sub New that can be used when you also want to include ' the inner exception. Public Sub New(Message As String, Inner As Exception) MyBase.New(Message) End Sub End Class
Note When using remoting in combination with user-defined exceptions, you must ensure that the metadata for your user-defined exceptions is available to code executing remotely, including exceptions that occur across application domains.
Samples of Structured Exception Handling
This code example is a simple Try...Catch block that first checks for ArithmeticException and then checks for generic exceptions.
Imports System Sub Main() Dim x As Integer = 0 Try Dim y As Integer = 100 / x Catch ex As ArithmeticException MessageBox.Show(ex.Message) Catch ex As Exception MsgBox(ex.Message) End Try End Sub 'Main
This code example is a Try...Catch...Finally block associated with an application that opens a file for examination. Note that the Finally statement gets executed even though Exit Sub appears before it in the code.
Imports System Sub OpenMyFile Dim thisFile As Object Try FileOpen(1, thisFile, OpenMode.Input) Catch ex As Exception MsgBox (ex.Message) Exit Sub Finally FileClose(1) End Try End Sub
Unstructured exception handling is implemented using the Err object and three statements: On Error, Resume, and Error. The On Error statement establishes a single exception handler that catches all thrown exceptions; you can subsequently change the handler location, but you can only have one handler at a time. The method keeps track of the most recently thrown exception as well as the most recent exception-handler location. At entry to the method, both the exception and the exception-handler location are set to Nothing.
To generate a run-time error in your code, use the Raise method. Whenever an Exit Sub, Exit Function, Exit Property, Resume or Resume Next statement occurs within an error-handling routine, the Err object's properties are reset to zero or zero-length strings. Using any of these outside an error-handling routine does not reset its properties. If you need to do so, you can use the Clear method to reset the Err object.
The Error Object
The values of the properties of the Err object are determined by the error that just occurred. The following table details the properties and provides a short description of each.
|Description||Text message providing a short description of the error.|
|HelpContext||Integer containing the context ID for a topic in a Help file.|
|HelpFile||String expression containing the fully qualified path to a Help file.|
|LastDLLError||System error code produced by a call to a dynamic-link library (DLL). This is the most recently called DLL before the error happened.|
|Number||Numeric value specifying an error.|
|Source||String expression representing the object or application that generated the error.|
The following example shows how to use some of these properties in unstructured error handling:
On Error Resume Next Err.Clear Err.Raise(33333) Err.Description = "You didn't input a number!" MsgBox(Err.Number) MsgBox(Err.Description) Msg = "Press F1 or HELP to see " & Err.HelpFile & " topic for" & _ " the following HelpContext: " & Err.HelpContext MsgBox(Msg)
The On Error GoTo Statement
The On Error GoTo statement enables a routine for exception handling and specifies the location of that routine within the procedure. Used with a label or line number, it directs the code to a specific exception handling routine. Used with -1, it disables error handling within the procedure. Used with 0, it disables the current exception. If there is no On Error statement and the exception is not handled by any methods in the current call stack, then any run-time error that occurs is fatal: execution stops and an error message is displayed.
This table shows the ways the On Error GoTo Statement may be used.
|On Error GoTo -1||Resets Err object to Nothing, disabling error handling in the routine|
|On Error GoTo 0||Resets last exception-handler location to Nothing, disabling the exception.|
|On Error GoTo <labelname>||Sets the specified label as the location of the exception handler|
|On Error Resume Next||Establishes the Resume Next behavior as the location of the most recent exception handler|
Resume and Resume Next
The Resume statement by itself can return control to the statement that caused the exception. The execution resumes at the same line that initially raised the exception.
By contrast, the Resume Next statement resumes execution after an exception has occurred. It specifies that in the event of an exception, control passes to the statement immediately following the statement in which the exception occurred. Resume Next can be used to allow graceful failures; the statement causing the error fails, but the application continues to execute and allows the user to correct the error and continue. Similarly, Resume <label> passes control to a label specified in its line argument. Make sure that the line label is located in the same procedure as the code calling it, since it cannot span between functions.
Resume must be used exclusively in error handling routines. Outside such routines, it causes an error.
The Error Statement
The Error statement is supported in Visual Basic .NET only for backwards compatibility. In new code, use the Err object's Raise method to generate run-time errors.
Samples of Unstructured Exception Handling:
The following example demonstrates a basic approach to unstructured error handling. When the Sub
FlawlessCode encounters an error, execution passes to
Whoops, which provides the user with information about the error, specifically what's contained in the Err Object's Description property:
Private Sub FlawlessCode() On Error GoTo Whoops ' Code doing various things ' Don't keep going into the error handling code. Return Whoops: ' Provide user with error information. MsgBox ("Unexpected Error:" & Err.Description) Return End Sub
The following example demonstrates how to use the Err object to construct an error-message dialog box.
Dim ErrorMessage As String ' Construct an error message if an error occurs. On Error Resume Next Err.Raise (13) ' Generate type mismatch error. ' Check to see if an error has occurred. If so, show message. If Err.Number <> 0 Then ErrorMessage = "Error # " & Str(Err.Number) & " was generated by " _ & Err.Source & vbCrLf & Err.Description ' Display the message as a critical message. MsgBox(ErrorMessage, MsgBoxStyle.Critical, "Error") End If
By now, you should have a good idea of the differences between unstructured and structured exception handling, as well as the advantages of the structured exception handling capabilities of Visual Basic .NET. Generally, structured exception handling will meet your needs, but under a few circumstances you might wish to use the unstructured alternative.
While you should make sure exceptions are handled, don't go overboard in throwing them, which can lead to a performance hit. Try structures are organized, easy to write and follow when reading, generate efficient code and should be used any time you have code in which you anticipate the possibility of one or more exceptions. This approach is so enticing, though, that the temptation occurs to use exceptions to control logic flow under normal conditions — for example, instead of an If or Select statement. Handling exceptions is efficient; throwing them should be reserved for genuine exception conditions.
If you wish to investigate exception handling in greater detail, the following three topics are good starting points:
The following Table lists the pre-defined exception classes, their causes, and their derived classes.
|Exception Class||Thrown when||Derived Classes|
|AppDomainUnloadedException||Attempt made to access an unloaded application domain||None|
|ArgumentException||One or more of the arguments provided to a method is not valid||ArgumentNullException
|ArithmeticException||Errors occur in an arithmetic, casting, or conversion operation||DivideByZeroException
|ArrayTypeMismatchException||Attempt made to store an element of the wrong type within an array||None|
|BadImageFormatException||File image of a DLL or executable program is invalid||None|
|CannotUnloadAppDomainException||Attempt to unload an application domain failed||None|
|Line number information is available for a serialization error||None|
|ComponentModel.LicenseException||A component cannot be granted a license||None|
|ComponentModel.WarningException||An exception is handled as a warning instead of an error||None|
|Configuration.ConfigurationException||An error occurs in a configuration setting||None|
|Configuration.Install.InstallException||An error occurs during the commit, rollback, or uninstall phase of an installation||None|
|ContextMarshalException||Attempt to marshal an object across a context boundary fails||None|
|Data.DataException||Errors are generated using ADO.NET components||Data.ConstraintException
|Data.DBConcurrencyException||During the update operation, the DataAdapter determines that the number of affected rows is equal to zero||None|
|Data.SqlClient.SqlException||SQL Server returns a warning or error||None|
|Data.SqlTypes.SqlTypeException||Base exception class for Data.SqlTypes||Data.SqlTypes.SqlNullValueException
|Drawing.Printing.InvalidPrinterException||Attempt made to access a printer using invalid printer settings||None|
|EnterpriseServices.RegistrationException||A registration error is detected||None|
|An error is detected in a serviced component||None|
|ExecutionEngineException||There is an internal error in the execution engine of the common language runtime||None|
|FormatException||The format of an argument does not meet the parameter specifications of the invoked method||Net.CookieException
|IndexOutofRangeException||Attempt made to access an element of an array with an index that is outside the bounds of the array||None|
|InvalidCastException||Invalid casting or explicit conversion||None|
|InvalidOperationException||A method call is invalid for the object's current state||Net.ProtocolViolationException
|InvalidProgramException||Programs contains invalid Microsoft intermediate language or metadata||None|
|IO.InternalBufferOverflowException||The internal buffer overflows||None|
|IO.IOException||An I/O error occurs||IO.DirectoryNotFoundException
|MemberAccessException||Attempt to access a class member fails||FieldAccessException
|MulticastNotSupportedException||There is an attempt to combine two instances of a non-combinable delegate type where neither operand is a null reference||None|
|NotImplementedException||A requested method or operation is not implemented||None|
|NotSupportedException||Invoked method is not supported or there is an attempt to read, seek, or write to a stream that does not support the invoked functionality||PlatformNotSupportedException|
|NullReferenceException||Attempt to dereference a null object reference||None|
|OutOfMemoryException||Not enough memory to complete the execution of a program||None|
|RankException||Array with the wrong number of dimensions is passed to a method||None|
|Binding to a method results in more than one method matching the binding criteria||None|
|The Module.GetTypes method determines that one or more of the classes in a module cannot be loaded||None|
|Main assembly does not contain the resources for the neutral culture, but they are required because of a missing appropriate satellite assembly||None|
|Base exception type for all COM interop exceptions and structured exception handling exceptions||ComponentModel.Design. |
|An invalid COM object is used||None|
|The marshaler encounters an argument of a variant type that cannot be marshaled to managed code||None|
|The marshaler encounters a MarshalAsAttribute that it does not support||None|
|Rank of an incoming SAFEARRAY does not match the rank specified in the managed signature||None|
|Type of an incoming SAFEARRAY does not match the type specified in the managed signature||None|
|Runtime.Remoting.RemotingException||Error occurs during remoting||Runtime.Remoting.Remoting |
|Runtime.Remoting.ServerException||Used to communicate exception when the client connects to a non-.NET framework application that cannot throw exceptions||None|
|Runtime.Serialization.SerializationException||An error occurs during serialization or deserialization||None|
|Security.Crytography.CryptographicException||An error occurs during a cryptographic operation||Security.Cryptography. |
|Security.Policy.PolicyException||Policy forbids code to run||None|
|Security.SecurityException||A security error is detected||None|
|Security.VerificationException||A security policy requires that code be type safe and the verification process is not able to verify that the code is type safe||None|
|Security.XmlSyntaxException||Syntax error in XML parsing||None|
|ServiceProcess.TimeoutException||A specified timeout has expired||None|
|StackOverflowException||Overflow of the execution stack due to too many pending method calls||None|
|Threading.SynchronizationLockException||A synchronized method is invoked from an unsynchronized block of code||None|
|Threading.ThreadAbortException||Call made to the Abort method||None|
|Threading.ThreadInterruptedException||Thread interrupted while in a WaitSleepJoin state||None|
|Threading.ThreadStateException||Thread in an invalid ThreadState for the method call||None|
|TypeInitializationException||Thrown as a wrapper around the exception thrown by the class initializer||None|
|TypeUnloadedException||Attempt to access an unloaded class||None|
|UnauthorizedAccessException||Operating system denies access because of an I/O error or a specific type of security error||None|
|Web.Services.Protocols.SoapException||Error occurs as a result of an XML Web service method being called over SOAP||Web.Services.Protocols. |
|Xml.Xpath.XpathException||Error occurs when processing an Xpath expression||None|
|Xml.Xsl.XsltException||Error occurs when processing an Extensible StyleSheet Language (XSL) transform||System.Xml.Xsl.XsltCompileException|