Microsoft’s .NET Framework provides structured exception handling, represented by a set of keywords and a dedicated exception class, for all programming languages. In Visual Basic, the keywords used to manage .NET exceptions include try, catch, finally, and throw. The .NET exception class provides a robust base from which you can manage errors efficiently, but you can also architect your own extensions that help you manage your custom objects effectively. Unlike the VB Err object, which contains information only about the current error, the exception class contains information on the current error and includes a list of errors that led up to the current error. This list can be interrogated at runtime.
Building reusable objects means that they need not manage only their own errors and throw meaningful errors to the programs that consume them. The .NET Framework allows the developer to extend the exception class through inheritance, making it possible for your object to return custom error information or provide more complex error handling while maintaining compatibility with other programs.
Unfortunately, many system architects and developers misuse this capability. Rather than using the standard event-handling mechanisms to pass information between objects, many developers use the exception management system to handle “expected events.” By definition, errors are “unexpected events,” and the exception management system should only be used to deal with runtime situations that are not predictable. Excessive misuse of the exception management system will make your programs unresponsive. Moreover, this makes your objects less likely to be reused because developers don’t want to deal with unneeded exceptions thrown by your objects.
Adding exception management to your systems
In the absence of any exception management, the .NET Framework will handle the error and throw up its standard error dialog. Not only does this give your users a lot of detail that they don’t need and can’t use, but it also gives them the option of pressing a continue button, which could have unintended side effects. In many simple scenarios, such as Device Not Ready or File Not Found, this may give users a way to keep the program going, but it doesn’t help you track down and fix errors. It won’t inspire confidence in the program among your end users, either.
In order to stand between your users and the runtime error management, you simply add a try keyword before the set of statements that could potentially generate an error, and then add one or more catch blocks to handle specific errors. Although the runtime lets you to leave the catch block empty (the equivalent of the Visual Basic On Error Resume Next), it’s still as bad an idea in .NET as it was in VB6. At a minimum, all catch blocks should capture the state of the current error in a variable, record the error, and then either handle it or prompt the user to do something to correct the error. You can decide how to handle the error by interrogating the exception object that the .NET Framework makes available when an error occurs.
Suppose an error occurs in a try block caught by this catch statement:
Catch exc as Exception
You can retrieve the error message from the exc object using the Message member (exc.Message). You can get the name of the application or object that caused the error with the Source member (exc.Source) and the actual method name (retrieved from the StackTrace) using the TargetSite member (exc.TargetSite).
In many cases, the error you’re handling has been triggered from another exception block in a different application or object. In order to deal with these nested exceptions, the exception class exposes two additional members. The InnerException member contains the information passed from the nested exception (exc.InnerException). This lets you tailor your response to both exceptions using detailed information about each. You may also want to tailor your response based on the chain of methods that brought you to this point in the program. The StackTrace member exposes this information to your program as a String (exc.StackTrace) that you can analyze from within your exception management code.
Exception order is important
In most systems, you’ll want to handle different errors in different ways. In order to accomplish this, you need to include a series of catch statements ordered from most specific to most generic. The CLR will send the exception to the first catch block that can handle the error based on their order listed in your program. For example, if you’re trying to handle errors that occur on the File.Open method, don’t list your catch blocks like this:
file open code
Catch exc as Exception
Code to handle generic exception
Catch exc as FileNotFoundException
Code to handle specific exception
No matter how many specific exceptions you list, the CLR will always match to the first generic exception handler.
Determine exception order in advance
One of the best contributions you can make as a systems architect is to provide guidance on how to handle different error conditions by helping developers determine the appropriate order in which to process exceptions. In the File.Open example discussed above, you’d have to handle the exceptions in the following order: ArgumentException, FileNotFoundException, ArgumentNullException, UnauthorizedAccessException, DirectoryNotFoundException, SecurityException, IOException, and finally Exception. As part of your design process, you should specify the order in which developers should check exceptions to make sure that the most specific exception type is always caught, recorded, and handled.