The structured exception-handling system used by .NET is a highly efficient and
powerful way to detect and handle errors. But if you've never dealt with
exceptions before—like most Visual Basic programmers looking at .NET—it can be a
little intimidating, possibly making you run back to your On Error Goto
roots. In this article, I'll introduce .NET's structured exception-handling
system and explain why it's superior to using return values and error codes.
="">What is an exception?
In simple terms, an exception represents an abnormal situation in an application—an "exceptional condition," if you will. Usually, this will be an error of some kind, and the exception itself will describe the error condition in some way. When an exception occurs in a program, it is passed up through the call chain until one of two things happens: Either a handler is found that is capable of handling the exception or the exception is tossed out of the application's main method, in which case, the default exception handler is triggered. This usually unceremoniously ends the program
Not surprisingly, in .NET, exceptions are represented as objects that ultimately
inherit from the System.Exception
object. Exceptions are said to be "thrown" when an error occurs and "caught"
when an error is handled. Handling an exception requires a code construct known
as a Try…Catch block, which, in its simplest form, looks like the code in
Your basic Try…Catch block in VB.NET and C#
Any exception occurring in code protected a Try block, even in a called
function or method that doesn't itself contain a Try block, will be
handled by the code inside the Catch block. Unless, of course, the
Catch block itself throws an exception, in which case it will be thrown
to the next higher Try block, even if that means throwing the exception
out of the currently executing function.
,b>The plot exceptionally thickens
Sounds pretty familiar so far: We've got an error-handling mechanism that will jump to an indicated error-handling block if an error occurs in the code protected by the handler. If no handler appears in the currently executing code, or if an error occurs in the handler, the error is propagated up the call stack. Here's where you run into the first disconnect between what you are used to and what you've got now—and it's called the Finally block.
The Finally block in a Try statement contains code that will
always be executed whenever an exception occurs. Including one is optional,
unless your Try does not have a matching Catch—a Try must
always end with one or the other. You'd place any cleanup code you wanted to
always execute, regardless of whether an error actually occurs, inside a
Finally block. This might include code to close files, database
connections, or other limited resources. Note that Finally code executes
immediately after Catch block code, unless an exception is thrown from
the Catch block.
Here are a few other things to keep in mind:
There is no equivalent for On Error Resume Next with exceptions,
although it's possible to jump out of a Try…Catch structure with an
Exit Try (VB.NET only) or Goto statement. Either option will still
execute any Finally block code. Try blocks can be nested within other Try blocks, although
doing so in the same function or method is not recommended. Multiple Catch statements are allowed for the same Try block
and are, in fact, encouraged over the use of multiple nested Try
statements, for reasons we'll get into in a moment.
Simultaneously handling multiple exceptionsFigure B
When handling errors in VB6, you usually had to resort to an If or
Case block that examined Err.Number if you wanted to figure out
exactly what error occurred. This often resulted in complex error-handling logic
that was itself prone to error and sometimes wound up being significantly longer
and more complex than the code it was protecting.
In .NET, you can
eliminate the If Err.Number=5647828312 syndrome by taking advantage of
the fact that all exceptions are objects and therefore have an intrinsic type
that can be compared to another type. A variation on the Catch statement
can take advantage of this ability to handle only a particular exception type,
as shown in Figure B.
Catching a particular exception type
You can set a Catch statement to be as specific or general as you like.
For example, the first Catch statement in Figure B will catch only
exceptions of type System.NullReferenceException,
the second will catch only exceptions descending from System.ArgumentException,
while the third will catch any exceptions not explicitly caught by the other
two. In this last case, the exception is explicitly thrown out of the current
Try block and would be handled in the next appropriate Catch block
found by the CLR as it crawled up the call stack. As it turns out, this final
Catch block is redundant, as that's the exact behavior you would see if
it were not included.
This short example illustrates four important rules about exceptions:
If multiple Catch blocks exist, the exception is thrown to the most
appropriate one based on the type of exception it is set to handle. If no acceptable Catch blocks are found, the exception is thrown out
of the current Try block to the next available Catch block found
up the call chain. The exception object's type gives important information about the nature of
the error that occurred. Exceptions may be explicitly thrown via the Throw
This is pretty cool stuff, allowing your code to selectively deal with only the
errors that it has the ability to deal with, trusting that others will be passed
up the call stack for notification purposes, at least. In practice, because of
the overhead involved in having the CLR watch for thrown exceptions, using a
single Try block with multiple exception-specific Catch
statements, as we saw in Figure B, is the best way to check for multiple
specific errors in a block of code.
Rolling your own exceptions
You can also create and throw exceptions in your code yourself to signal the
occurrence of system-defined errors without having them actually occur. All of
the exception classes provide overloaded constructors, allowing you to include
specific information about the cause of the exception. It's usually a good idea
to do so and to include at least an explanatory message when creating the
The astute among you will have deduced that if one can
explicitly create and throw a system-defined exception, one must also be able to
create and throw one's own exceptions. These custom exceptions usually represent
application-defined errors, and should always extend the System.ApplicationException
Replacing the often-ignored return code
At one time or another, we've all written code that relies on functions returning meaningful error codes to inform the caller of the nature of an error, should one occur. The main problem with this strategy is that it's possible for the caller to neglect to check the return code and erroneously assume that a call succeeded. Using exceptions makes it impossible to ignore the fact that an error occurred in a function call. If the caller fails to catch an exception thrown by the function it called, it is completely bypassed, and execution moves up the call chain to the last exception handler that was encountered.
A second problem is that, unless you are careful to ensure otherwise, there can be little rhyme or reason to what return codes equate to which error conditions. Developers sometimes deal with this problem by assigning return code ranges or prefixes to particular families of error messages. Exceptions, by virtue of their status as full-fledged objects, have meaning in their specific types. They can have an inheritance hierarchy as simple or as complex as required to enable you to handle them as either specific error conditions or as related groups of errors.
Breaking the Goto habit
Depending on your point of view, the fact that On
Error error handling is alive and well in VB.NET is either fortunate or
unfortunate. It works exactly as it did with your VB6 code and has been
thoroughly updated to be compatible with .NET's structured exception system.
Yeah, that's right, you can still use the good old Err object and not
miss a beat because it exposes a GetException method that returns a
System.Exception object representing whatever the current error might be.
The Err.Raise method throws true exceptions that can be caught by
Try blocks (even those written in other .NET languages) and On
Error statements alike. It's even possible to catch exceptions by their
equivalent error number in VB.NET, further blurring the line between exception
handling and error handling.
I tend to fall into the group that considers
the persistence of On Error in VB.NET unfortunate. I actually find the
fact that VB.NET's error-handling system is so schizophrenic to be a bit of an
embarrassment. The truth is that this is a horrible mishmash of error-handling
functionality that has been kept around simply for backward compatibility, and
I, for one, hope it won't be supported for long. It's also unique to VB.NET and
therefore won't be found in any other .NET languages. So sticking with the
VB6-style error handling means you are tied to VB.NET and lose one of the big
advantages of .NET development—the freedom to use the best language for the
You see, sometimes, change can be good.