.NET Exceptions

Make the Transition from Traditional Visual Basic Error Handling to the Object-Oriented Model in .NET

Jesse Liberty

This article assumes you're familiar with Visual Basic .NET

Level of Difficulty123

SUMMARY

If you're used to Visual Basic 6.0 and you're making the transition to Microsoft .NET, you will find that error handling is quite different from what you've used for years. Visual Basic .NET uses a more object-oriented solution to signaling and responding to unexpected problems while your program is running. This approach, called structured exception handling, has a number of advantages over the On Error statements provided in previous versions of Visual Basic. For instance, exceptions give you lots more information about exactly what went wrong in your app. To help you take advantage of this modern error handling paradigm, this article will show you how to raise and respond to exceptions, as well as how to create your own custom exception classes.

Contents

Throw and Catch
Specialized Exceptions
The Finally Block
Exception Properties
Custom Exceptions
Conclusion

Bad things happen to good programs. You run out of memory, files are lost or corrupted, network connections are dropped. Although these are not bugs, they can still bring an app to its knees.

There's no way to predict when any of these situations will occur and you cannot prevent them from happening. Nevertheless, your program must be prepared to deal with them. Ideally, when an exception arises you'll want to handle it silently and then let your program continue (for example, you can try the connection again in the hopes that this time it won't time out). Failing that, you may want to offer the user the opportunity to correct the situation, perhaps by closing other programs to free up memory or to release a lock on a file. You may even need to exit gracefully. What you don't want to do is just crash.

Most programmers using Visual Basic® 6.0 respond to problems using unstructured exception handling. This is the familiar technique of placing an On Error statement at the beginning of a block of code. Errors are either ignored (using Resume Next) or handled in an error handling block:

Sub mySub On Error GoTo ErrorHandler ' code that may cause a problem here Exit Sub ErrorHandler: ' Error handling code here Resume End Sub

The Microsoft® .NET Framework uses exceptions rather than error codes to signal that something untoward has happened in your program. Exceptions are objects that encapsulate an irregular circumstance, such as when an application is out of memory. As such, they can provide details about what went wrong, such as the location of the offending code, an error message, or a link to help files. They can also take action by writing the error message to a log file. Exceptions are thrown when a problem arises and are caught in exception handlers.

Exceptions can be thrown across language boundaries so that code in a C#-based program can throw an exception that you can catch in your Visual Basic .NET method.

In this article I will explore how exceptions are used in Visual Basic .NET and will demonstrate some of the power that's available to you through the inheritance of exceptions and the mechanism of polymorphism.

Throw and Catch

The .NET Framework will throw exceptions for you if circumstances are such that code running in one of the .NET Framework classes cannot continue. Exceptions can include anything from running out of memory, a file that cannot be opened, or an attempted illegal cast. You can also throw an exception from within your own code using the keyword Throw.

To catch exceptions, enclose your dangerous code within a Try block and follow the Try block with a Catch block. When an exception is thrown within the Try block, execution halts where it is, and the common language runtime (CLR) searches for a Catch block. If none is found in the method in which the exception was thrown, the stack is unwound to the calling method. If a Catch block is found that matches the exception in the calling method, the exception is handled there, and execution resumes on the first statement following the Catch block. You should note that if the stack is unwound, execution does not return to the method from which the exception was thrown.

Figure 1 contains a simple Try/Catch block. This code allows you to encase a potentially dangerous action (calling Sub2) in a Try block. Within Sub2, the code manually throws an exception. Sub2 will not have an exception handler and so the stack will be unwound and the exception will be caught up in Sub1. One way that you can make this very clear to yourself is to put the code into Visual Studio® .NET and step through it in the debugger, as shown in Figure 2.

Figure 2 Stepping through the Code

Figure 1 A Try/Catch Block

imports System ' this line only required for manual compilation Class Tester Shared Sub Main() Console.WriteLine("Entering Main...") Dim t As New Tester() t.Run() Console.WriteLine("Exiting Main...") End Sub 'Main Public Sub Run() Console.WriteLine("Entering Run...") Sub1() Console.WriteLine("Exiting Run...") End Sub 'Run Public Sub Sub1() Console.WriteLine("Entering Sub1...") Try Console.WriteLine("Entering Try block...") Sub2() Console.WriteLine("Exiting Try block...") Catch Console.WriteLine("Exception caught and handled") End Try Console.WriteLine("Exiting Sub1...") End Sub 'Sub1 Public Sub Sub2() Console.WriteLine("Entering Sub2...") Throw New System.Exception() Console.WriteLine("Exiting Sub2...") End Sub 'Sub2 End Class 'Tester

Output:

Entering Main... Entering Run... Entering Sub1... Entering Try block... Entering Sub2... Exception caught and handled Exiting Sub1... Exiting Run... Exiting Main...

Note that I've chosen to put all my code in classes rather than in modules. To use this code with Visual Studio .NET, create a new Visual Basic .NET console application and delete the default Visual Studio .NET code before pasting in the code sample in Figure 1. Build the application and double-click on the error: "'Sub Main' was not found." Choose the Tester class as the startup object and run the application.

In order to compile the program from a command prompt, enter the source code in a text editor and save the file as (for example) TryCatch.vb. Open a command prompt, go to the directory containing the source code file, and then enter the following command:

vbc /out:TryCatch.exe /t:exe /r:system.dll TryCatch.vb

In this example, an exception of the type System.Exception is thrown in Sub2. Notice from the output that once this exception is thrown, processing never returns to Sub2 and the line "Exiting Sub2..." is never displayed.

The stack is unwound and the exception is caught in the Catch block in Sub1. This Catch statement is generic; it does not specify what type of exceptions it catches, and so it catches them all. Once the exception is caught and handled, processing resumes on the next line after the Catch block in Sub1, and the statement "Exiting Sub1..." is displayed.

All exceptions must derive from System.Exception. The Framework Class Library provides a number of derived Exception classes that you can throw and catch. Some of the more useful standard exceptions are shown in Figure 3.

Figure 3 Standard System Exceptions

Exception Type Description
Exception Base type for all exceptions
IndexOutOfRangeException Thrown when you try to access an array index improperly
NullReferenceException Thrown when you try to access a null reference
InvalidOperationException Thrown when a class is in an invalid state
ArgumentException Thrown when you pass an invalid argument to a method; see also ArgumentNullException and ArgumentOutOfRangeException
DivideByZeroException Thrown when you divide by zero
ArithmeticException Thrown for general arithmetic errors

Specialized Exceptions

You are free to create specialized exception handling routines that handle only some exception types and not others. To see how this works, create code that throws the DivideByZero exception if you attempt to divide any number by zero:

Public Function MyDivider( ByVal a As Double, ByVal b As Double) As Double If b = 0 Then Throw New System.DivideByZeroException() End If Return a / b End Function

Your calling method wraps its call to MyDivider in a Try block and then catches the DivideByZeroException:

Try ' call dangerous code here Catch e As System.DivideByZeroException ' handle divide by zero End Try

It turns out that a full blown MyDivider (not shown in this article) might throw other exceptions as well, such as the more general ArithmeticException. You can order your Catch blocks so that more specialized exception handlers can catch the most common exceptions:

Try ' call dangerous code here Catch e As System.DivideByZeroException ' handle divide by zero Catch e As System.ArithmeticException ' handle general arithmetic errors Catch ' handle other exceptions End Try

The Catch order is important. Once an exception is caught, no other Catch block will see it. You want to order your exception handlers from most specialized to most generic.

If you were to reverse the order of the first two Catch blocks, you would never enter the DivideByZeroException handler. The DivideByZeroException class derives from the ArithmeticException. Therefore, every DivideByZeroException is an ArithmeticException, so the exception would be caught in the ArithmeticException handler. In the order just shown, however, the DivideByZero exception would be caught by the appropriate handler, and only other non-DivideByZero Arithmetic exceptions would filter down to the second handler. The complete source code for this example is shown in Figure 4.

Figure 4 Ordering Exception Handlers

imports System ' this line only required for manual compilation Class Tester Public Sub Run() Try Dim a As Double = 5.5 Dim b As Double = 0 Console.WriteLine("Dividing {0} by {1}...", a, b) Console.WriteLine("{0} / {1} = {2}", _ a, b, MyDivider(a, b)) ' most derived exception type first Catch e As System.DivideByZeroException Console.WriteLine("DivideByZeroException caught!") Catch e As System.ArithmeticException Console.WriteLine("ArithmeticException caught!") Catch ' generic exception handler last Console.WriteLine("Unknown exception caught") End Try End Sub ' do the division if legal Public Function MyDivider( _ ByVal a As Double, ByVal b As Double) As Double If b = 0 Then Throw New System.DivideByZeroException() End If Return a / b End Function ' MyDivider Public Shared Sub Main() Console.WriteLine("Enter Main...") Dim t As Tester = New Tester() t.Run() Console.WriteLine("Exit Main...") End Sub End Class

Output:

Enter Main... Dividing 5.5 by 0... DivideByZeroException caught! Exit Main...

The Finally Block

If you have opened a file or otherwise committed a resource, exceptions can create major problems for your application. You must ensure that the file is closed or the resource is freed whether or not you throw an exception. One approach is to enclose the dangerous action in a Try block and then to close the file in both the Catch and Try blocks. However, you'll be duplicating code, and sooner or later you'll update one part of the code and forget to update the other. Visual Basic .NET provides a better alternative: the Finally block.

Code in a Finally block is guaranteed to be executed regardless of whether an exception is thrown. The recognized procedure is to open the file (or allocate the resource) in the Try block and to close the file (and deallocate the resource) in the Finally block. If no exception is thrown, the Finally block will execute after the Try block. If an exception is thrown, the Finally block will execute after the exception handler (if any).

Figure 5 shows a test scenario in which opening a file is simulated in the Try block, and the file is closed in the Finally block. If you comment out the exception being thrown, you'll find that the file close is called in either case.

Figure 5 Opening and Closing a File in Try Block

imports System ' this line only required for manual compilation Class Tester Public Sub Run() Try Console.WriteLine("Simulating file open...") Dim a As Double = 5 Dim b As Double = 0 Console.WriteLine("{0} / {1} = {2}", a, b, MyDivider(a, b)) Console.WriteLine("This line may or may not print") ' catch blocks Catch e As System.DivideByZeroException Console.WriteLine("DivideByZeroException caught!") Catch e As System.ArithmeticException Console.WriteLine("ArithmeticException caught!") Catch Console.WriteLine("Unknown exception caught!") ' finally block is executed whether or not exception is ' thrown Finally Console.WriteLine("Simulating file close...") End Try End Sub 'Run ' do the division if legal Public Function MyDivider( _ ByVal a As Double, ByVal b As Double) As Double If b = 0 Then Throw New System.DivideByZeroException() End If Return a / b End Function 'MyDivider Shared Sub Main() Console.WriteLine("Enter Main...") Dim t As New Tester() t.Run() Console.WriteLine("Exit Main...") End Sub 'Main End Class 'Tester

Output:

Enter Main... Simulating file open... DivideByZeroException caught! Simulating file close... Exit Main...

Exception Properties

In the scenarios I just considered, the Exception object is just a sentinel; its very presence signals the occurrence of an exception. You have not called any exception methods or examined any properties of the Exception object itself. It turns out that System.Exception provides a number of useful methods and properties. For example, the Message property provides information about why the exception was thrown. The Message property is read-only and cannot be modified, but it can be set by passing a string to the exception constructor.

A second useful property is the HelpLink, which provides a link to a help file associated with the exception. The HelpLink property is read/write. The StackTrace property is read-only and you can't set it at all since it is controlled by the CLR. The StackTrace is used to display the call-stack at the time the exception is thrown. The call stack shows the series of method calls that lead to the method from which the exception was thrown.

Figure 6 shows a simple example that sets the Message and HelpLink property of an exception and then uses both, along with the StackTrace, to provide useful information to the user.

Figure 6 Setting Message and HelpLink Property

imports System ' this line only required for manual compilation Class Tester Public Sub Run() Try Console.WriteLine("Simulating file open...") Dim a As Double = 5 Dim b As Double = 0 Console.WriteLine("{0} / {1} = {2}", a, b, MyDivider(a, b)) Console.WriteLine("This line may or may not print") ' catch blocks Catch e As System.DivideByZeroException Console.WriteLine( _ "DivideByZeroException! Msg: {0}", e.Message) Console.WriteLine( _ "Helplink: {0}", e.HelpLink) Console.WriteLine( _ "Stack trace: {0}", e.StackTrace) Catch e As System.ArithmeticException Console.WriteLine("ArithmeticException caught!") Catch Console.WriteLine("Unknown exception caught!") ' finally block is executed whether or not exception is ' thrown Finally Console.WriteLine("Simulating file close...") End Try End Sub 'Run ' do the division if legal Public Function MyDivider( _ ByVal a As Double, ByVal b As Double) As Double If b = 0 Then Dim e As New System.DivideByZeroException() e.HelpLink = "https://www.LibertyAssociates.com" Throw e End If Return a / b End Function 'MyDivider Shared Sub Main() Console.WriteLine("Enter Main...") Dim t As New Tester() t.Run() Console.WriteLine("Exit Main...") End Sub 'Main End Class 'Tester

Output:

Enter Main... Simulating file open... DivideByZeroException! Msg: Attempted to divide by zero. Helplink: https://www.LibertyAssociates.com Stack trace: at ConsoleApplication1.Tester.MyDivider(Double a, Double b) in C:...\Module1.vb:line 41 at ConsoleApplication1.Tester.Run() in C:...\Module1.vb:line 8 Simulating file close...

In this version, rather than simply throwing an instance of System.DivideByZeroException, you create the instance and then assign a value to its HelpLink property:

Dim e As New System.DivideByZeroException() e.HelpLink = "https://www.LibertyAssociates.com" Throw e

When you catch the exception, you access its various properties to display their values to the user:

Catch e As System.DivideByZeroException Console.WriteLine( _ "DivideByZeroException! Msg: {0}", e.Message) Console.WriteLine( _ "Helplink: {0}", e.HelpLink) Console.WriteLine( _ "Stack trace: {0}", e.StackTrace)

Custom Exceptions

All exceptions in .NET derive from System.Exception. The CLR defines two types immediately derived from System.Exception: System.SystemException and System.ApplicationException. All exceptions thrown in your application will derive from System.ApplicationException. The System.SystemException class is used to differentiate exceptions thrown by your application and those thrown by the system. SystemException exceptions are thrown by the CLR.

The exception types provided by the CLR will typically be all you'll need to tackle most situations. In the event that you require a special exception type that's not provided by the CLR, however, you are free to create your own by deriving it from System.ApplicationException:

Public Class MyCustomException Inherits System.ApplicationException

Within your custom exception, you'll want to provide (at a minimum) a constructor that takes a message, which you can chain up to the base class:

Public Sub New(ByVal message As String) MyBase.New(message) End Sub 'New

The System.Exception constructor is overloaded, so a second version of the constructor takes a message and an instance of System.Exception called the inner exception:

Public Sub New(ByVal message As String, ByVal inner As Exception) MyBase.New(message, inner) End Sub 'New

When an exception is handled, the Catch block has the option to rethrow the exception. You do so with the keyword Throw. Rethrowing unwinds the stack to the calling method, and if there is an appropriate Catch block, the rethrown exception is caught there. This allows you to do some work in your first exception handler and then rethrow the exception to the calling method for further work to be done there:

Catch e As System.ArithmeticException ' do some work here, then re-throw Throw

Rather than simply rethrowing the same exception, you might process the exception and then create a new exception of a different type. This allows you to provide additional information in the new exception. You can include the original exception in the new exception as an inner exception, thus providing the handler with all the information from the original exception (as well as the new information in your new exception). You create the inner exception by passing the original exception to the constructor of the new exception:

Catch e As System.DivideByZeroException Dim ex As New Exception("E2 - Func2 caught divide by zero", e) Throw ex

This code snippet catches a DivideByZeroException (named e) and creates a new exception of type System.Exception. The exception handler then passes in two arguments to the constructor of the new Exception object (named ex): a message and the original DivideByZeroException (e). And finally, it then throws this new exception handler.

Just as you can create instances of a framework exception, you are free to create instances of your own custom exception type. And if you've overloaded the constructor to take the same two, as shown in this snippet arguments, you can pass in a message and an inner exception:

Catch e As System.Exception Dim ex As New MyCustomException("E3 - Custom Exception Situation!", e) Throw ex

Once you catch the new exception, you can display its message:

Console.WriteLine("{0}", e.Message)

You can open the exception to find its inner exception and display that message. Like Russian nesting dolls, each exception may have its own inner exception. You can loop through these inner exceptions, displaying each message in turn, like this:

Dim inner As Exception = e.InnerException While Not (inner Is Nothing) Console.WriteLine("{0}", inner.Message) inner = inner.InnerException End While

This is demonstrated in Figure 7, where you have five methods. Run calls DangerousFunc1, which calls DangerousFunc2, which calls DangerousFunc3 which in turn calls DangerousFunc4. The last, DangerousFunc4, throws a DivideByZeroException and includes the message, "DivideByZero Exception."

Figure 7 Exceptions Within Exceptions

imports System ' this line only required for manual compilation Public Class MyCustomException Inherits System.ApplicationException Public Sub New(ByVal message As String) MyBase.New(message) End Sub 'New Public Sub New(ByVal message As String, ByVal inner As Exception) MyBase.New(message, inner) End Sub 'New End Class 'MyCustomException Public Class Test Public Shared Sub Main() Dim t As New Test() t.Run() End Sub 'Main Public Sub Run() Try DangerousFunc1() ' if you catch a custom exception ' print the exception history Catch e As MyCustomException Console.WriteLine("{0}", e.Message) Console.WriteLine("Retrieving exception history...") Dim inner As Exception = e.InnerException While Not (inner Is Nothing) Console.WriteLine("{0}", inner.Message) inner = inner.InnerException End While End Try End Sub 'TestFunc Public Sub DangerousFunc1() Try DangerousFunc2() ' if you catch any exception here ' throw a custom exception Catch e As System.Exception Dim ex As New MyCustomException("E3 - Custom Exception Situation!", e) Throw ex End Try End Sub 'DangerousFunc1 Public Sub DangerousFunc2() Try DangerousFunc3() ' if you catch a DivideByZeroException take some ' corrective action and then throw a general exception Catch e As System.DivideByZeroException Dim ex As New Exception("E2 - Func2 caught divide by zero", e) Throw ex End Try End Sub 'DangerousFunc2 Public Sub DangerousFunc3() Try DangerousFunc4() Catch e As System.ArithmeticException Throw Catch e As System.Exception Console.WriteLine("Unknown exception caught in DangerousFunction3") End Try End Sub 'DangerousFunc3 Public Sub DangerousFunc4() Throw New DivideByZeroException("E1 - DivideByZero Exception") End Sub 'DangerousFunc4 End Class 'Test

Output:

E3 - Custom Exception Situation! Retrieving exception history... E2 - Func2 caught divide by zero E1 - DivideByZero Exception

DangerousFunc3 has an exception handler that catches the DivideByZeroException and rethrows it. It is caught in an exception handler in DangerousFunc2, which creates a new Exception object and includes, as an argument to the constructor, the details of the exception it has caught:

Catch e As System.DivideByZeroException Dim ex As New Exception("E2 - Func2 caught divide by zero", e)

The constructor for the new exception will stash e away as an inner exception. This new exception is thrown and then caught by an exception handler in DangerousFunc1, where a custom exception is created, and the exception caught is again passed in to create the inner exception. That custom exception is thrown and then caught in Run.

Finally, in Run, the custom exception is caught and its message is displayed. The inner exception is then extracted and its message is displayed. That inner exception's inner exception is then extracted and its message is displayed in turn.

Since that innermost exception does not have its own inner exception, the test inner is Nothing evaluates true and the while statement ends.

While Not (inner Is Nothing)

Conclusion

It's not very difficult to understand how to use exceptions; the tough part is writing code that uses exceptions well. The trick is to write good exception handlers that manage the problem with minimal disruption for the user.

It is generally considered poor programming practice to use exceptions for anything except unusual but anticipated problems that are beyond your programmatic control (such as losing a network connection). Exceptions should not be used to handle programming bugs. A manager I once worked for had the following useful advice: "Save time. Don't put in the bugs in the first place." Exceptions should also not be used as a quick way to unwind the stack ("finished here, throw an exception to unwind the stack and get back to the top.") They should be reserved for situations in which something big went wrong that you'd better handle.

And you should always try to remember, while garbage collection makes working with exceptions much easier, you'll need to be very careful about making sure that files and other resources are disposed of in a Finally block.

For related articles see:
Basic Instincts: Using Inheritance in the .NET World

Jesse Libertyis the author of a dozen books on .NET and object-oriented software development. He is the president of Liberty Associates Inc. (https://www.LibertyAssociates.com), where he provides contract programming and training in .NET. This article was adapted from his book, Learning Visual Basic .NET published by O'Reilly & Associates Inc. (October 2002).