The .NET Class Library uses exceptions throughout to report
errors or unexpected conditions; however, a lot of code isn’t
exception aware. This article explains what exceptions are, how
to respond to exceptions, and several ways of creating your own
exceptions.
What’s an Exception?
Here’s a one sentence description of an exception: an
exception is a means of control designed to keep an application’s
data in a consistent state during unexpected circumstances. In
plain English this means that exceptions work to make your
applications more reliable in three ways:
- Increase the visibility of errors
- Provide a centralized means of handling errors
- Provide a means of handling errors where most
appropriate
Your application is considered to be in a consistent state
when the data it maintains (its own variables, data in a
database, etc) is free of unusual or invalid values. For example,
when you withdraw money from your bank account, the bank’s
accounting system makes two entries: debit your personal account
and credit the bank’s cash/income account. If the system makes
only one entry (for example, debit your personal account) and
then crashes the bank’s data is considered to be in an
inconsistent state because only it recorded only half of a
complete transaction (an invalid entry).
A 15 second accounting lesson
If you’re not familiar with double entry accounting, read
this section.
From the bank’s perspective, the money in your account is a
liability (they owe you the money in your bank account); you
decrease the value of a liability by using a debit transaction.
When you put money into the bank you increase the bank’s
liability to you (they owe you more money), which translates into
a credit. When you take money out, you reduce the bank’s
liability, which translates into a debit transaction. Since most
accounting systems are based on the double entry system, where
debits equal credits, a bank withdrawal must involve some other
account (in this case, an asset account). You reduce the value of
an asset using a credit – thus the credit to the bank’s cash
account.
Exceptions make errors more visible
One of the ways that exceptions work to make your applications
more reliable is by making errors more visible. For example, when
you allocate some memory using the JScript .NET new operator the system looks for a free
block of memory and makes it available to your application. What
would be an appropriate response from the system when it cannot
accommodate your request? Should it return null, 0, or should it return something
like a negative number? Regardless of what the system does,
chances are that most developers will write code like this:
var myMessage : String = new String("Hello World"); // new never fails since my system has 512Mb of memory... Console.WriteLine(myMessage);
Although most developers agree that something could go wrong
with the preceding code, many of us don’t bother to check the
result of every action. Ignoring return values can lead to
problems regardless of what platform or programming language you
use and exceptions simply work to make errors more visible. The
.NET Framework could throw an OutOfMemoryException if you run the
preceding fragment on a system that cannot allocate any more
memory. When your application does not handle, or respond, to an
exception on its own, the .NET Framework handles it for you by
promptly terminating your application. The .NET Framework’s
default exception handler terminates your application because no
data is better than bad data. If you don’t like the default
behavior you’ll be interested in the rest of this article,
otherwise be prepared to handle calls from your customers about a
big message box appearing on their screen when they try to use
your application.
Exceptions provide a centralized means of handling errors
If you have been programming for a while, you’ll probably be
familiar with code that’s full with if statements that check the result of
every function call, operations, and any other code that could
result in a failure. While the approach is good, represents a
double-edged sword because the code becomes more reliable at the
cost of increasing the code’s complexity which makes it more
difficult to maintain – complex and difficult to maintain code
are factors that eventually lead to the a decrease in an
application’s reliability.
Statements like if and
switch are considered to be
local control structures since both statements make a decision
and take action within the same scope. For example, consider the
following JScript .NET fragment:
if ( myMessage.Length == 0) { // do something } else { // do something else } // rest of program here...
The if statement determines
of the Length property of the
myMessage object is zero and
takes an action immediately following the statement. This is a
local control structure because the code that makes the decision
and the code that carries out the resulting action reside within
the same scope (area of the application). Exceptions, in
contrast, are non-local control structures. For example, consider
the following fragment:
if (myMessage.Length == 0) throw InvalidArgumentException(); // rest of program follows...
The code still evaluates the Length of the myMessage object; however, it does not
handle the error condition locally – it instead passes control to
some other part of the application using the throw statement. When the throw statement executes, the .NET Runtime
intervenes and queries the application for a suitable error
handler (I’ll discuss how to create an error handler shortly) –
if the application cannot handle the error then the .NET Runtime
invokes its own default error handler (which terminates your
application).
If you want to check for certain conditions throughout your
application using if
statements, you have no choice but to sprinkle all of your code
with if statements thereby distributing error handling throughout
your code. Using an exception allows you to centralize error
handling making it easier to figure out where error handlers are
and making them easier to maintain without increasing the
complexity of your application.
Exceptions provide a means of handling errors where most appropriate
Assume that the fragment that uses the if statement resides deep in some part of
your application. When the statement executes, chances are
that you’ll want to notify the user or ask the user to correct
the problem; the problem with this approach is that you have to
“bubble up” the error condition to some part of your application
that can handle the error using some type of protocol you devise
yourself. The following fragment illustrates how a developer
could handle errors that occur deep within an application:
function main() { var userInput : Appointment; userInput = getInformationFromUser(); while(!writeInformationToDatabase(userInput)) { userInput = getInformationFromUser(); if (userInput.Action == action.Exit) break; } } function writeInformationToDatabase(appointmentInfo : Appointment) { //.. if(!connectToDatabase()) { MessageBox.Show("Cannot connect to database!"); return false; } if(!saveToDatabase(appointmentInfo)) { MessageBox.Show("Database not available"); return false; } if(!disconnectDatabase()) { MessageBox.Show("Cannot disconnect from database!"); return false; } return true; } function saveToDatabase(appointmentInfo : Appointment) { //.. return false; }
The fragment shows an error occurs in the saveToDatabase function, which gets passed
on to the writeInformationToDatabase function, and
finally ends up in main. Using
this approach requires that all functions understand how error
reporting works within the application – information that’s often
irrelevant to the functionality they implement. Exceptions allow
you to centralize error reporting into a single location, as
shown in the following fragment:
function main() { var userInput : Appointment; try { userInput = getInformationFromUser(); writeInformationToDatabase(userInput); } catch ( argException : ArgumentException) { //... } catch ( dbException : databaseConnectionException ) { //... } catch ( e : Exception ) { //... } } function writeInformationToDatabase(appointmentInfo : Appointment) { //.. connectToDatabase()) { saveToDatabase(appointmentInfo()); disconnectDatabase() return true; } function saveToDatabase(appointmentInfo : Appointment) { //.. throw (new databaseConnectionException()); }
The fragment shows that the main function is responsible for handling
all errors – the rest of the application either throws new
exceptions, or simply executes its own functionality. When an
exception occurs, the .NET Runtime bubbles-up the exception
through the application until it finds a catch statement that’s
capable of handling the exception (in the main function).
Handling Exceptions
It’s easier to understand exceptions by first learning how to
handle them. All .NET exceptions derive from the System.Exception class; a class that’s
designed to be used for all .NET Framework-based exceptions. As
previously discussed, when an exception occurs (any exception,
not just ones that the .NET Framework generates) the .NET Runtime
intervenes and looks through your application’s code for a
handler that’s willing to process (handle) the exception. If the
framework does not find an appropriate handler it invokes its own
default handler, which terminates your application. The reason I
mentioned that all .NET-based exceptions derive from a single
class is that it is important how you order your exception
handlers in your code – I’ll describe this further in a
moment.
There are two statements that you initially use to work with
exceptions: try and catch. Here’s an example of how to
use both statements:
var s : StringBuilder; try { s = new StringBuilder(1000); } catch (argException : ArgumentOutOfRangeException) { //... } catch (memException : OutOfMemoryException) { //... } catch (genericException : Exception) { //... }
The try statement delimits
a section of code which anticipates that an exception could
occur. A catch statement
delimits a block of code that’s capable of handling a specific
type of exception, as defined by its single parameter. When code
within a try block encounters
and exception, the .NET Runtime inspects the exception to
determine its type and reviews each catch statement’s parameter to determine
if it can find a match. Note the order in which the catch statements appear – they are written
from the most specific type of exception (the first catch statement in the listing) to the
least specific type (the last catch statement in the listing). The
reason that the catch
statements are ordered in this way relates to classes and
inheritance. Recall that you can treat inherited types as if they
are base types (in other words, you can replace a derived type
with a base type). If you reverse the order of the catch statements in the listing (by
putting the last catch
statement first), only the first statement will ever execute
because all .NET-based exceptions derive from the System.Exception class.
Using the finally statement
There is another aspect of the try and catch statements called the finally statement. The role of finally statement is to execute
unconditionally after the code in a try block or a try and catch block executes, as shown in the
following figure:
The finally bock of code is guaranteed to execute in all
cases, so it is very useful for releasing resources like database
connections or objects that use a lot of memory.
Raising an exception – the throw statement
There are cases where your code cannot handle an exception.
For example, you write a catch
block to catch the System.Exception type; however, on closer
inspection you realize that you cannot handle the exception. The
way to handle a situation like this is simply to re-raise the
exception to allow the runtime to look through the rest of your
code to see if there’s another handler capable of handling the
exception. You re-raise an exception using the throw statement, as shown in the following
listing:
try { //... } catch ( generalException : Exception) { // Cannot deal with the problem here... throw generalException; }
The throw statement takes a
single parameter: the type of exception you want to raise. You
can use the new operator to
create an object before you throw it; however, you run the risk of not
catching an OutOfMemory
exception in case the system is low on memory.
Creating your own exception types
The .NET Class Library includes a class called System.ApplicationException which is
reserved for applications – the .NET Framework does not raise any
exceptions based on this type; however this class also derives
from System.Exception so you
still have to order your catch statements properly. The following
listing demonstrates how to create, throw, and handle your own
exception types:
class myInvalidArgumentException extends System.ApplicationException { function myInvalidArgumentException() { // default constuctor } function myInvalidArgumentException( msg : String ) { // create a new exception based on a message } function myInvalidArgumentException( msg : String , innerEx : Exception ) { // This constructor gets called as part of a chain of exceptions. // The innterEx parameter gets populated with another exception // in the chain of exceptions } } function buggyFunction() { try { doSomethingBad(); } catch (myException : myInvalidArgumentException) { //... } catch (allOtherExceptions : Exception) { // catch all other exceptions here... } } function doSomethingBad() { throw (new myInvalidArgumentException("This is really bad"); }
The listing demonstrates how to create a custom exception by
using the extends keyword to
derive the new class from the System.ApplicationException class. The
class has three constructors, as shown in the listing. The first
two constructors should be present for all exception classes you
create since it makes them easy to use with the new operator. The last constructor is
there to accommodate exceptions that exist as part of a chain of
exceptions. The buggyFunction
demonstrates how to handle both the application-specific
exception and all other exceptions. The doSomethingBad function
demonstrates how to create an exception based on a String-based
message, using the class’s second constructor.
Summary
This article gave you an overview of what exceptions are, what
they’re used for, how to handle them, and how to create your own
exceptions. Exceptions are a broad field – there are entire books
that cover just exceptions. One excellent resource, for C++
programmers, is “Exceptional C++” by Herb Sutter (ISBN
0201615622) – this is a great resource for C++ programmers who
use JScript .NET.
Essam Ahmed is the author of “JScript .NET Programming” (ISBN 0764548689, Published by Hungry Minds September 2001), many
articles (including some at CodeGuru.com) and book reviews (also available at CodeGuru.com).
Contact Essam at essam@designs2solutions.com,
or at his Web site