An exception is an error that occurs at runtime. Using C#’s exception handling subsystem, you can, in a structured and controlled manner, handle runtime errors. C#’s approach to exception handling is a blend of and improvement on the exception handling mechanisms used by C++ and Java. Thus, it will be familiar territory to readers with a background in either of these languages. What makes C#’s exception handling unique, however, is its clean, straightforward implementation.
A principal advantage of exception handling is that it automates much of the error-handling code that previously had to be entered “by hand” into any large program. For example, in a computer language without exception handling, error codes must be returned when a method fails, and these values must be checked manually, each time the method is called. This approach is both
Another reason that exception handling is important is that C# defines standard exceptions for common program errors, such as divide-by-zero or index-out-of-range. To respond to these errors, your program must watch for and handle these exceptions.
In the final analysis, to be a successful C# programmer means that you are fully capable of navigating C#’s exception handling subsystem.
In C#, exceptions are represented by classes. All exception classes must be derived from the built-in exception class Exception , which is part of the System namespace. Thus, all exceptions are subclasses of Exception .
From Exception are derived SystemException and ApplicationException . These support the two general categories of exceptions defined by C#: those generated by the C# runtime system (that is, the CLR) and those generated by application programs. Neither SystemException nor ApplicationException adds anything to Exception . They simply define the tops of two different exception hierarchies.
C# defines several built-in exceptions that are derived from SystemException . For example, when a division-by-zero is attempted, a DivideByZeroException is generated. As you will see later in this chapter, you can create your own exception classes by deriving them from ApplicationException .
C# exception handling is managed via four keywords: try , catch , throw , and finally . They form an interrelated subsystem in which the use of one implies the use of another. Throughout the course of this chapter, each keyword is examined in detail. However, it is useful at the outset to have a general understanding of the role each plays in exception handling. Briefly, here is how they work.
Program statements that you want to monitor for exceptions are contained within a
try
block. If an exception occurs within the
try
block, it is
thrown.
Your code can catch this exception using
catch
and handle it in some rational manner. System-generated exceptions are automatically thrown by the C# runtime system. To manually throw an exception, use the keyword
throw
. Any code that
At the
try { // block of code to monitor for errors } catch (
ExcepType1 exOb
) { // handler for
ExcepType1
} catch (
ExcepType2 exOb
) { // handler for
ExcepType2
} . . .
Here, ExcepType is the type of exception that has occurred. When an exception is thrown, it is caught by its corresponding catch statement, which then processes the exception. As the general form shows, there can be more than one catch statement associated with a try . The type of the exception determines which catch statement is executed. That is, if the exception type specified by a catch statement matches that of the exception, then that catch statement is executed (and all others are bypassed). When an exception is caught, exOb will receive its value.
Actually, specifying exOb is optional. If the exception handler does not need access to the exception object (as is often the case), there is no need to specify exOb. For this reason, many of the examples in this chapter will not specify exOb.
Here is an important point: If no exception is thrown, then a try block ends normally, and all of its catch statements are bypassed. Execution resumes with the first statement following the last catch . Thus, catch statements are executed only if an exception is thrown.
Here is a simple example that illustrates how to watch for and catch an exception. As you know, it is an error to attempt to index an array beyond its boundaries. When this occurs, the C# runtime system throws an IndexOutOfRangeException , which is a standard exception defined by C#. The following program purposely generates such an exception and then catches it:
// Demonstrate exception handling. using System; class ExcDemo1 { public static void Main() { int[] nums = new int[4]; try { Console.WriteLine("Before exception is generated."); // Generate an index out-of-bounds exception. for(int i=0; i < 10; i++) { nums[i] = i; Console.WriteLine("nums[{0}]: {1}", i, nums[i]); } Console.WriteLine("this won't be displayed"); } catch (IndexOutOfRangeException) { // catch the exception Console.WriteLine("Index out-of-bounds!"); } Console.WriteLine("After catch statement."); } }
This program displays the following output:
Before exception is generated. nums[0]: 0 nums[1]: 1 nums[2]: 2 nums[3]: 3 Index out-of-bounds! After catch statement.
Notice that
nums
is an
int
array of four elements. However, the
for
loop
Although quite short, the
Notice that no parameter is specified in the catch clause. As mentioned, a parameter is needed only when access to the exception object is required. In some cases, the value of the exception object can be used by the exception handler to obtain additional information about the error, but in many cases it is sufficient to simply know that an exception occurred. Thus, it is not unusual for the catch parameter to be absent in the exception handler, as is the case in the preceding program.
As explained, if no exception is thrown by a try block, no catch statements will be executed, and program control resumes after the catch statement. To confirm this, in the preceding program, change the for loop from
for(int i=0; i < 10; i++) {
to
for(int i=0; i < nums.Length; i++) {
Now, the loop does not
It is important to understand that all code executed within a
try
block is
For example, consider the following program. Main( ) establishes a try block from which the method genException( ) is called. Inside genException( ) , an IndexOutOfRangeException is generated. This exception is not caught by genException( ) . However, since genException( ) was called from within a try block in Main( ) , the exception is caught by the catch statement associated with that try .
/* An exception can be generated by one method and caught by another. */ using System; class ExcTest { // Generate an exception. public static void genException() { int[] nums = new int[4]; Console.WriteLine("Before exception is generated."); // Generate an index out-of-bounds exception. for(int i=0; i < 10; i++) { nums[i] = i; Console.WriteLine("nums[{0}]: {1}", i, nums[i]); } Console.WriteLine("this won't be displayed"); } } class ExcDemo2 { public static void Main() { try { ExcTest.genException(); } catch (IndexOutOfRangeException) { // catch the exception Console.WriteLine("Index out-of-bounds!"); } Console.WriteLine("After catch statement."); } }
This program produces the following output, which is the same as that produced by the first version of the program shown earlier:
Before exception is generated. nums[0]: 0 nums[1]: 1 nums[2]: 2 nums[3]: 3 Index out-of-bounds! After catch statement.
As explained, since genException( ) is called from within a try block, the exception that it generates (and does not catch) is caught by the catch in Main( ) . Understand, however, that if genException( ) had caught the exception, then it would never have been passed back to Main( ) .