Visual Studio 6.0
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

Error Handling the VB.NET Way: Living with Exceptions

Gary Cornell and Jonathan Morrison

In Debbie Cooper's article this month, she shows how to use API functions to retrieve error numbers and error messages in VB6. Here, Gary Cornell and Jonathan Morrison illustrate how VB.NET's new structured error handling is going to change how we code. This article is adapted from their forthcoming book, Programming VB.NET: A Guide for Experienced Programmers (Apress, ISBN 1893115992).

Unfortunately, bad things can happen to good programs: A network connection might go down, the printer might run out of paper, and so forth. As the programmer, it's not your fault when these things happen, but it's not the user's fault when the network goes down, either. At the very least, your program must do the following:

  • Log or in some way notify the user of the problem
  • Allow the user to save his or her work if appropriate
  • Allow users to gracefully exit the program if they think it necessary

Of course, this is easier said than done. The code that tried to open the network connection typically isn't going to be attached to the objects whose state you need to maintain, and you'll usually also need some way to transfer control as well as inform other objects what happened so they can deal with the situation too.

Other programming languages have had what's usually called structured exception handling for years, but not VB—until VB.NET. No, don't worry, structured exception handling won't break your On Error syntax. However, we honestly think you'd be foolish to continue using it for new programs.

Error checking vs. exception handling

Traditional error checking (certainly that used in current versions of VB and in traditional COM or Windows programming) is done by first checking the return value of a given function and then doing something based upon that value. This usually involves the equivalent of implementing a giant switch statement that checks the value returned by the function. Unfortunately, the return value itself sometimes seems to be random. Sometimes a "1" is good, sometimes it isn't. Sometimes a "0" means success, sometimes failure. And in the sample code given here, the value returned can seem truly random:

Select Case ErrorNumber
   Case 57
      MsgBox "Your printer may be off-line."
   Case 68
      MsgBox "Is there a printer available?"
…
Case Else
  ' oops 
End Select

Although this kind of code gets the job done, it's fair to say that it's hard to read and harder to maintain. We think it's also fair to say there's a lot of room for programmer error in this scheme. For instance, suppose that you made a mistake with one of the error values or, as is all too common, forgot to check all of the possible return values of the error function. Plus, it's just a pain to write the same error-checking code every time you use a Windows API function. In particular, while there are times when you'll have to check the return value of a function regardless of what type of error-handling scheme you're using, you don't want to do it everywhere.

Here's a good analogy to help you understand the difference between error checking and structured exception handling. Think of structured exception handling as the way traffic control works: Police patrol the streets looking for vehicles that break the law. If a police officer sees you breaking the law, he or she will pull you over and give you a ticket. The process of the police officer giving you a ticket is analogous to structured exception-handling code throwing an exception in an application—most of the time, things proceed pretty efficiently and you aren't bothered. Now imagine the case for error checking by looking at the return value of a function. This would be analogous to your having to stop every time you saw a police officer and ask him or her if you've broken the law (or worse yet, "Did I break Law 1? No? What about Law 2?…"). Now, even though this scenario might seem a little bit contrived, if you think it through you should start to see the benefit of using structured exception handling over error checking alone. For example, one key benefit is efficiency: Exception-handling code costs you less time in writing, less time in maintenance, and often less time in executing!

First steps in exception handling

Before we start writing the code that shows you some exception handlers at work, you have to keep in mind that when you use structured exception handling you're providing an alternative path for your code that will be executed automatically when something bad happens. More precisely, VB.NET allows you to create in every piece of code inside a method an alternate path for code when code is unable to complete its work in the normal way. If this happens, VB.NET automatically creates an object that encapsulates the error information, and the built-in exception-handling mechanism begins its search for a handler that can deal with that particular object (error condition). It's important to keep in mind that what we're describing isn't a bunch of "GoTos" (see the sidebar) that make for spaghetti code—it's more like the service road that runs parallel to the main highway.

Next, keep in mind that, in a way, this "service road" is the dream of all drivers who are stuck in traffic. It's a smart service road—if something goes wrong, you'll automatically be shunted to the exception-handling code sequence. (Well, you will be if you wrote the code for the exception handler.) Once you're on the "service road," the code in the exception handler can deal with the problem or, optionally, let it "bubble up." This means the exception handler passes the exception object on to the code that called the code that caused the exceptional condition.

The actual mechanism for doing this in VB.NET is called a Try-Catch block. Here's an example: Suppose you build a Console application called ProcesssFile. The user is supposed to use this application from the command line by typing something like this:

ProcessFile nameoFile 

As users are prone to do, he or she can do one of many annoying things to your nice program:

  • Forget to give you a filename
  • Give you a non-existent file
  • Ask you to work with a file that's locked for this operation
  • And so on and so forth...

We want to write the code that assumes we have a real user in place who will do his or her best to frustrate us. For this example, we'll use the nifty .NET Framework method System.Environment.GetCommandLineArgs(), which returns an array of strings that are the command line arguments. The 0th entry in this array is the path name of the application, but index 1 corresponds to the name of the file we want to use. Here's an example of the simple Try-Catch block in a VB.NET application that could be part of the ProcessFile application:

Module Exception1
   Sub Main()
      Dim args() As String
      Dim argument As String
      args = Environment.GetCommandLineArgs()
      Try
         ProcessFile(args(1))
      Catch
         Console.WriteLine("ERROR")
      End Try
      Console.WriteLine("Press enter to end")
      Console.ReadLine()
   End Sub
   Sub ProcessFile(ByVal fName As String)
      'process file code goes here
      Console.WriteLine("Am processing " & fName)
   End Sub
End Module

The code in the Try section of the Try-Catch block is assumed to be "good" code—in this case, a call to ProcessFile. The code in the Catch section of the Try-Catch block is there because, well, users are users. In this code snippet, if the user forgot to enter a filename, the code would try to reference the name of the file and cause an IndexOutOfRangeException. This would cause the flow to move down the alternate pathway (the Catch block), which simply prints out ERROR in the command window.

The next step is not only to catch the exception but also to analyze it, and we do this by modifying the key catch line to read:

Catch e As Exception

Now the exception object referenced by "e" contains an incredible amount of information. For example, change the code in the Catch clause to read:

Catch e As Exception
Console.WriteLine(e)

in order to take advantage of the built-in ToString method of the exception object e, and you'll see the following, which, of course, says it all if the user forgot to enter a filename!

System.IndexOutOfRangeException: An exception of type 
System.IndexOutOfRangeException was thrown. 
at Exception_1.Exception1.Main() in C:\Documents and 
Settings\x20\My Documents\Visual Studio Projects
\ConsoleApplication14\Exception1.vb:line 6

(Not that we recommend printing this information out for users—unless you want to scare them, but that's another issue.)

Finally, while reading this code, we hope you're thinking ahead. Suppose the user supplied a filename but the ProcessFile method couldn't process it. Then what? Well, the Catch clause can be made more sophisticated. Furthermore, the ProcessFile method can even throw an exception object back to Main that encapsulated what went wrong in its work. Oh, and yes, you're even allowed to have multiple Catch clauses, as follows:

Sub Main()
   Dim args(), argument As String
   args = Environment.GetCommandLineArgs()
   Try
      ProcessFile(args(1))
   Catch indexProblem As IndexOutOfRangeException
      Console.WriteLine("ERROR - No file name _
         supplied")
   Catch ioProblem As System.IO.IOException
      Console.WriteLine("ERROR - can't process file _
         named " & args(1))
   End Try
   Console.WriteLine("Press enter to end")
   Console.ReadLine()
End Sub 

Note that no other code that you wrote in any other Catch blocks inside the same Try statement will be processed once a match is made.

Some tips on using exceptions

Exceptions are cool, but it's all too easy to overuse them—at the cost of performance. Here are three tips on using exceptions, but they all come down to variations on the rule that exceptions are supposed to be exceptional. In other words, remember that exception handling isn't supposed to replace testing for the obvious. You don't, for example, use exceptions to test for EOF conditions. Similarly:

  • Don't micromanage exceptions by wrapping every possible statement in a Try-Catch block. It's usually better to wrap the whole action in a single Try statement than to have multiple Try statements.
  • Don't squelch exceptions by writing code like this without a very good reason:

Catch e as Exception

  • This is the equivalent of blindly using On Error Resume Next in older VB code and is bad for the same reasons. If an exception happens, either handle it or propagate it.

Which leads to the final rule—what we like to call "the good fellowship rule" for exceptions:

  • If you don't handle an exceptional condition completely and need to rethrow an exception to the calling code, add enough information (or create a new exception class) so that the code you're communicating with knows exactly what happened and what you did to (try to) fix it.

With that, we'll close, Microsoft-style, with a "call to action": Get exceptional.

Download CORNELL.TXT

Sidebar: The Infamous "GoTo"

Well-known Dutch computer scientist Edsger W. Dijkstra (author of several college texts) opined against "GoTos" in his famous and often-cited classic article "Go To Statement Considered Harmful" in 1968. You can read the original on the ACM Web site at www.acm.org/classics/oct95/. And here's the reference if you ever need to cite it yourself: Communications of the ACM, Vol. 11, No. 3, March 1968, pp. 147-148.

Dijkstra was the 1972 ACM Turing award winner (do a Google search or refer to www.turing.org.uk if you're not familiar with Alan Turing and the Turing Test). Dijkstra's ACM citation explains how, in the late 1950s, he was one of the principal contributors to the development of the ALGOL and that he's "one of the principal exponents of the science and art of programming languages in general, and has greatly contributed to our understanding of their structure, representation, and implementation. His 15 years of publications extend from theoretical articles on graph theory to basic manuals, expository texts, and philosophical contemplations in the field of programming languages."

(You can read a slightly more extensive bio at http://henson.cc.kzoo.edu/~k98mn01/dijkstra.html, where you'll also find bios of other computer science pioneers.)

Another giant figure in computer science is Donald Knuth, and his rebuttal ("Structured Programming with GOTO Statements," Computing Surveys, Volume 6, 1974, pp. 261-301) to Dijkstra's comments on GoTos is almost as famous as the original document. By the way, Professor Knuth, the inventor of the TEX typesetting language and author of the classic "Art of Programming" series of books on algorithms, has a home page at www-cs-faculty.stanford.edu/~knuth/, and there's a funny cartoon about him and his alleged book, Donald Knuth's Big Dummies Guide to Visual Basic, at www.ibiblio.org/Dave/Dr-Fun/df200002/df20000210.jpg.

To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at http://www.pinpub.com/

Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.

This article is reproduced from the June 2001 issue of Visual Basic Developer. Copyright 2001, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.

Show: