developmentor - a developer services company				  
				      C# Tutorial
   Modules

Exceptions


Goals:

  • Discuss C# exception handling
  • Cover keywords try, catch, throw, and finally.
  • Introduce a few of the .NET Framework Library exception types
  • Show how to create a custom exception.

Overview

Traditional error notification involves methods reporting success or failure through a return value, a global variable, or by invoking an error handler. Exceptions are an alternative to these traditional techniques. The main benefit of using exceptions is that it separates the point of error detection from the handler code in a very clean way.


Exception hierarchy

Exceptions must be classes derived from the library class Exception. There are two broad subcategories, one for exceptions generated by the CLR and the .NET Framework (the system exceptions) and one for user defined exceptions (the application exceptions).

The Exception class offers some basic services. The two most generally useful features are the read-only properties Message and StackTrace. The error message is set by the code that creates the exception while the stack trace is filled in automatically. All exception types inherit the services of the Exception class, plus they are free to add more information specific to the type of condition they represent.

The .NET Framework class library defines many exception types for common error conditions. These exceptions are typically used by the CLR or library classes to report errors back to the application code. By convention, the class names all end in Exception.


Handling an exception

The CLR and .NET Framework Class Library throw exceptions to indicate errors. For example, the CLR will throw an IndexOutOfRangeException when an invalid array index is used. To handle an exception, the client must place their code inside a try/catch construct. The real work goes inside the try block while the error handling code is placed in the associated catch block. The control flow through a try/catch is fairly straightforward. When an exception is thrown, the normal control flow is stopped and the remainder of the try block will be skipped. Control jumps to the catch block and the statements in the block are executed. When the execution of the catch block has finished, linear control flow resumes from the line of code after the catch block. There is no way to automatically return to where the exception occurred and continue from that point.

A few more details about what goes on might be of interest. When the CLR or library code throws an exception, they actually create an object of the appropriate type and stuff it full of interesting information about what went wrong. In the example above, the CLR would instantiate an IndexOutOfRangeException object. A reference to this object is passed to the handler and appears in what looks like a method parameter. Glance back at the catch block shown above and notice the thing named xcpt. That is a reference to the exception object that was passed to the handler and is used to access the information inside the exception object. There is nothing special about the name xcpt, the naming of the reference is completely up to the programmer who codes the catch block.


Multiple catch

A try block may have multiple associated catch blocks. Each catch block includes a parameter that specifies the type of the exception that it is designed to handle. catch blocks are always chosen based on the type of the exception, so if an IndexOutOfRangeException is active, then the catch block for that type will be executed. Only one catch block will be executed from the set. After a catch has been run, the others will be skipped and linear control flow will resume past the end of the entire try/catch construct.


Generating an exception

The keyword throw is used to raise an exception. The object that is thrown must be of a derived type of the library Exception class. In the following Divide method, we generate a DivideByZeroException if the denominator passed to the method is zero. Notice how a string is passed as the constructor argument when the exception object is created. This string will become the error message that can be retrieved by clients using the Message property. When a throw statement is executed, normal control flow is halted and the search for a matching handler begins.


Locating a handler

Suppose our program begins execution in Main. The Main method then calls a method named One. One calls a method named Two. Two calls a method named Three. Three calls Divide and passes zero for the denominator argument. The Divide method will throw a DivideByZeroException and the search for a handler will begin. This situation is summarized in the figure below.

The search for a handler will begin within the Divide method itself. The code for Divide is shown below. Notice that there are no try/catch constructs so no handler is available. Since no handler was found, the Divide method will be exited and all the memory used for its execution will be reclaimed (parameters, local variables, etc.). The search for a handler will continue one level up the call chain in the method Three.

The story inside the method Three is similar to Divide in that there are no try/catch constructs so no handler is available. Once again the search must move up the call chain to the method Two.

Two looks more promising since it at least has a try/catch construct. However, the catch is not of the correct type so it is ignored and the search continues with the method One.

One contains a try/catch construct and has a catch of the correct type. The matching handler is executed and the exception is finished.

If the search had unwound the call chain all the way to the top without finding a matching handler, the program would have been terminated. Many systems have a default handler at the top level that will print out some of the information in the exception such as the message and/or the stack trace.


Catching base class

Exception classes are typically organized into inheritance hierarchies. For example, a portion of the standard exception hierarchy is shown below with the base class IOException and two of its derived types FileNotFoundException and EndOfStreamException.

When a catch clause specifies a base class, the handler will match that base class and all its derived classes. This is another example of the is-a relationship in action.

A catch block can be made generic by specifying an Exception reference as the type in the handler. Since Exception in the root of the exception hierarchy this will catch all exceptions.


Finally

Some resources require cleanup. The classic example is a disk file where the usage pattern is the familiar open/access/close. It is sometimes difficult to use this type of class in the presence of exceptions since an exception being thrown could cause the close operation to be skipped. This situation is shown in the example code below which makes use of the .NET Framework Library class FileStream. Notice that there is no handler in the method so any exception that is generated will immediately leave the method in search of a handler. When the exception leaves the method, the rest of the code will be skipped and the close method of the file will not be called.

The keyword finally is used to create a block of code that gets executed regardless of whether or not an exception is generated. This makes it perfect for any needed cleanup code such as closing disk files. A finally block is placed at the end of the try/catch construct. The classic setup is a try block, several catch blocks, and then a finally block. It is also possible to have a try/finally construct without any catch blocks. The sample code below demonstrates the syntax.

The control flow through a try/catch/finally construct can get a little tricky since there are a surprising number of cases to consider. The main thing to keep in mind though, is that the code in the finally block is always execute when control leaves the try/catch above it.

To avoid getting bogged down in too much detail, let's just examine two cases based on the try/finally code above. First case: the try block completes normally without throwing an exception. In this case, after control leaves the try block, it jumps down and executes the finally. After executing the finally block, normal linear control flow is resumed with whatever code follows the finally block. Second case: part way through the try block an exception is thrown. In this case, control skips the rest of the code in the try block and jumps down to the finally block. After finishing the finally block control will leave the method in search of an handler for the exception. There are a number of other cases, but they should be relatively simple to construct and test if needed.


Custom exception

To create a custom exception type, simply write a class derived from the library class ApplicationException. Convention dictates that the class name should end in Exception.

By convention, a custom exception should offer a constructor that takes a string which represents the error message. The string can be passed to the base class constructor for storage by the Exception class. The custom exception type can also contain any fields needed to store addition information that might be of interest to the client code that will handle the exception.

This material is excerpted from the Programming C# course offered by DevelopMentor.