Error Handling in .NET Languages
Author Dino Esposito explains how the .NET Framework and the .NET Common Language Runtime (CLR) can greatly assist you in designing error-resistant software.
December 10, 2001
For a long time, C and C++ developers deeply envied their Visual Basic (VB) colleagues for VB's On Error Goto and On Error Resume Next features. At a certain point, the notion of structured exception handling surfaced in C++ applications, but many people still considered this feature "not for everyone" and good for only elite programmers. Along the way, COM came out, but it only reinforced this concept.
COM introduced developers to the intricacies of the HRESULT data type. Any COM automation object method—and in general, any function a COM interface exposes—must return an HRESULT value to denote its outcome and add information about how the operation developed and completed.
Before the advent of .NET languages, Windows programmers had to use techniques other than structured exceptions for handling application errors and, more often than not, even a new programming model—the event-based programming model. Using a structured exception handler is quite different from having the code return an HRESULT value whose bits have been properly set to reflect certain information.
If you employ exception handlers, you assume in advance that a certain piece of code is a potential problem source. So you put that code within a sort of transactional black box from which two possible results can emerge. Either the code completes successfully, or the control passes to another piece of code that's supposed to handle the error and degrade gracefully. The error handler might also contain logic that lets it recover from the error and take a different route to the code. What matters with exception handling is that the subroutine you write to handle an error situation is logically and physically associated with the source code that can originate the error. As a developer, you don't have to worry about the main flow of the application. The compiler is responsible for placing an appropriate JUMP instruction wherever needed; this instruction guarantees the correct flow of the mainstream code. With structured exception handling, you wrap the potentially dangerous code with a handler that can recover if something goes wrong.
In COM, you know whether a method terminated successfully from the HRESULT value that you get back. HRESULT is a 32-bit numeric value in which every bit plays a specific role. The COM software development kit (SDK) makes available several functions and macros for creating HRESULT values and extracting from them the various tokens of significant information. From the programmer's point of view, this approach sounds less flexible in that you have to first run the code and then check the return value for every method. You can see that code based on an HRESULT-driven approach can be effective and error-safe only if it's interspersed with continuous if() statements that ensure that nothing bad happens. On the other hand, structured exception handling lets you decide the granularity you want and need. There's no predefined size for the block you want to control—be it the entire application or one function call.
In VB and VBScript, the On Error statement represents a sort of compromise. This statement is easy—if not straightforward—to use, but it isn't as powerful and complete as the structured exception-handling mechanism. By contrast, using the On Error statement is more programmer-friendly than testing the return value of a code. VB internally manages the HRESULT values that calls return to COM object methods. VB exposes the HRESULT values to the caller application as error events that the programmer can catch through the language-native constructs such as On Error.
Note that in VBScript the misuse of On Error Resume Next has been the main cause of significant bugs and flaws in Web applications. The lesson you can learn from this is that errors are a serious matter, and just ignoring them is simple to code but hardly solves the real problem.
In .NET, Microsoft tried to make all the programming languages use structured exceptions for error handling. So now you find the same constructs and the same programming power in C#, as well as in Visual Basic .NET, in JScript, and in Managed C++.
The most important statement for error handling is Try. It wraps the code that you suspect might originate a certain type of exception. If an error is raised while any line of this block is executing, the control passes to the Catch block that is expected to recover from the error.
Try and Catch identify two mutually exclusive branches of code, much like the If-Then and the If-Else statements. Whatever output such trapped code produces, the lines you specify in the optional Finally clause will always execute. Here's an example.
try { // statements } catch(Exception e) { // also catch() {...} // handle exception using information from "e" } finally { // statements that always run after try/catch }
The programmer can alert the Catch block on a certain type of exception. In .NET, the base class for runtime errors is Exception. The Catch block detects an error if the error is expressed through a class that's compatible with the class you specified as the Catch block's argument. All .NET-specific exception classes derive from Exception. For this reason, the following code
Try {...} Catch(Exception e) {...}
catches any exception that might be raised. More exactly, it can catch any error that's represented through an Exception class. If your Try code makes an unmanaged call into a COM object, there's no way to use exceptions to detect the error, because COM uses HRESULT values to make error notifications.
When an application needs to raise an exception that's due to an internal inconsistent status, it uses the Throw method.
public int function MyMethod(Object o) { if (o == null) throw(new ArgumentNullException()); }
An exception needs an object of a class that's derived from Exception, whose properties the application can fill as appropriate with information pertinent to the error that occurred. The error handler catches an error only if you put the code that can raise the relative exception into a Try block. Also, you need a Catch block that listens for that particular exception class or a parent class. A Try block can have more Catch blocks, each looking for a different type of exception. For example,
Try {...} Catch(ArgumentNullException ena) {...} Catch(Exception e) {...} Finally {...}
When you specify multiple Catch blocks, they're called in the order in which they appear in the code. For this reason, include the most specific exceptions first.
After years of On Error Goto, HRESULT, Boolean flags, and Windows exception handlers, a uniform, consistent, and effective way to handle errors is more than welcome—especially if it works in the same way across several different languages.
The .NET Framework and the .NET Common Language Runtime (CLR) can greatly assist you in designing error-resistant software. The CLR eliminates many frequent errors that result from improper uses of pointers and type conversions. In addition, the CLR provides a platform for notifying programs of errors in a uniform way, even if the error occurs in a module written in a different language from the module that's catching the error. The entire .NET Framework has just one way to indicate failure: by throwing exceptions.
About the Author
You May Also Like