Like many object-oriented languages, C# handles abnormal conditions with exceptions. An exception is an object that encapsulates information about an unusual program occurrence.
It is important to distinguish between bugs, errors, and exceptions. A bug is a programmer mistake that should be fixed before the code is shipped. Exceptions aren’t a protection against bugs. Although a bug might cause an exception to be thrown, you should not rely on exceptions to handle your bugs. Rather, you should fix the bugs.
An error is caused by user action. For example, the user might enter a number where a letter is expected. Once again, an error might cause an exception, but you can prevent that by catching errors with validation code. Whenever possible, errors should be anticipated and prevented.
Even if you remove all bugs and anticipate all user errors, you will still run into predictable but unpreventable problems, such as running out of memory or attempting to open a file that no longer exists. You can’t prevent exceptions, but you can handle them so that they don’t bring down your program.
When your program encounters an exceptional circumstance, such as running out of memory, it throws (or “raises”) an exception. When an exception is thrown, execution of the current function halts, and the stack is unwound until an appropriate exception handler is found (see the sidebar, "Unwinding the Stack“).
This means that if the currently running function doesn’t handle the exception, the current function will terminate, and the calling function will get a chance to handle the exception. If none of the calling functions handles it, the exception will ultimately be handled by the CLR, which will abruptly terminate your program.
An exception handler is a block of code designed to handle the exception you’ve thrown. Exception handlers are implemented as catch
statements. Ideally, if the exception is caught and handled, the program can fix the problem and continue. Even if your program can’t continue, by catching the exception, you have an opportunity to print a meaningful error message and terminate gracefully.
If there is code in your function that must run regardless of whether an exception is encountered (e.g., to release resources you’ve allocated), you can place that code in a finally
block, where it is certain to run, even in the presence of exceptions.
In C#, you can throw only objects of type System.Exception
, or objects derived from that type. The CLR System
namespace includes a number of exception types that your program can use. These exception types include ArgumentNullException, InvalidCastException
, and OverflowException
, as well as many others.
C++ programmers take note: in C#, not just any object can be thrown—it must be derived from System.Exception
.
To signal an abnormal condition in a C# class, you throw an exception. To do this, use the keyword throw
. This line of code creates a new instance of System.Exception
and then throws it:
throw new System.Exception( );
Throwing an exception immediately halts execution of the current “thread” (see Chapter 21 for a discussion of threads) while the CLR searches for an exception handler. If an exception handler can’t be found in the current method, the runtime unwinds the stack, popping up through the calling methods until a handler is found. If the runtime returns all the way through Main( )
without finding a handler, it terminates the program. Example 11-1 illustrates.
using System; namespace Programming_CSharp { public class Test { public static void Main( ) { Console.WriteLine("Enter Main..."); Test t = new Test( ); t.Func1( ); Console.WriteLine("Exit Main..."); } public void Func1( ) { Console.WriteLine("Enter Func1..."); Func2( ); Console.WriteLine("Exit Func1..."); } public void Func2( ) { Console.WriteLine("Enter Func2..."); throw new System.ApplicationException( ); Console.WriteLine("Exit Func2..."); } } } Output: Enter Main... Enter Func1... Enter Func2...
When you run this program in debug mode, an “Exception was unhandled” message box comes up, as shown in Figure 11-1.
If you click View Detail, you find the details of the unhandled exception, as shown in Figure 11-2.
This simple example writes to the console as it enters and exits each method. Main( )
creates an instance of type Test
and call Func1( )
. After printing out the Enter Func1
message, Func1( )
immediately calls Func2( ). Func2( )
prints out the first message and throws an object of type System.Exception
.
Execution immediately shifts to handling the exceptions. The CLR looks to see whether there is a handler in Func2( )
. There is not, and so the runtime unwinds the stack (never printing the exit
statement) to Func1( )
. Again, there is no handler, and the runtime unwinds the stack back to Main( )
. With no exception handler there, the default handler is called, which opens the exception message box.
In C#, an exception handler is called a catch block and is created with the catch
keyword.
In Example 11-2, the throw
statement is executed within a try
block, and a catch
block is used to announce that the error has been handled.
using System; using System.Collections.Generic; using System.Text; namespace CatchingAnException { public class Test { public static void Main( ) { Console.WriteLine("Enter Main..."); Test t = new Test( ); t.Func1( ); Console.WriteLine("Exit Main..."); } public void Func1( ) { Console.WriteLine("Enter Func1..."); Func2( ); Console.WriteLine("Exit Func1..."); } public void Func2( ) { Console.WriteLine("Enter Func2...");try { Console.WriteLine("Entering try block..."); throw new System.ApplicationException( ); Console.WriteLine("Exiting try block..."); } catch { // simplified for this book; typically you would // correct (or at least log) the problem Console.WriteLine("Exception caught and handled."); } Console.WriteLine("Exit Func2..."); } } } Output: Enter Main... Enter Func1... Enter Func2... Entering try block... Exception caught and handled. Exit Func2... Exit Func1... Exit Main...
Example 11-2 is identical to Example 11-1 except that now the program includes a try/catch
block.
It is a common mistake to clutter your code with try/catch
blocks that don’t actually do anything and don’t solve the problem that the exception is pointing out. It is good programming practice to use a try/catch
block only where your catch
has the opportunity to rectify the situation (with the exception of the topmost level where, at a minimum, you want to fail reasonably gracefully).
An exception to this practice is to catch and log the exception, and then rethrow it for it to be handled at a higher level, or to catch the exception, add context information, and then nest that information bundled inside a new exception, as described later in this chapter.
Catch
statements can be generic, as shown in the previous example, or can be targeted at specific exceptions, as shown later in this chapter.
One of the most important purposes of a catch
statement is to take corrective action. For example, if the user is trying to open a read-only file, you might invoke a method that allows the user to change the attributes of the file. If the program has run out of memory, you might give the user an opportunity to close other applications. If all else fails, the catch
block can log the error (or even send out email) so that you know specifically where in your program you are having the problem.
Examine the output of Example 11-2 carefully. You see the code enter Main( ), Func1( ), Func2( )
, and the try
block. You never see it exit the try
block, though it does exit Func2( ), Func1( )
, and Main( )
. What happened?
When the exception is thrown, the normal code path is halted immediately and control is handed to the catch
block. It never returns to the original code path. It never gets to the line that prints the exit
statement for the try
block. The catch
block handles the error, and then execution falls through to the code following catch
.
Without catch
, the call stack unwinds, but with catch
, it doesn’t unwind, as a result of the exception. The exception is now handled; there are no more problems, and the program continues. This becomes a bit clearer if you move the try/catch
blocks up to Func1( )
, as shown in Example 11-3.
using System;
namespace CatchingExceptionInCallingFunc
{
public class Test
{
public static void Main( )
{
Console.WriteLine("Enter Main...");
Test t = new Test( );
t.Func1( );
Console.WriteLine("Exit Main...");
}
public void Func1( )
{
Console.WriteLine("Enter Func1...");try
{
Console.WriteLine("Entering try block...");
Func2( );
Console.WriteLine("Exiting try block...");
}
catch
{
Console.WriteLine(
"Unknown exception caught when calling Func 2.");
}
Console.WriteLine("Exit Func1...");
}
public void Func2( )
{
Console.WriteLine("Enter Func2...");
throw new System.ApplicationException( );
Console.WriteLine("Exit Func2...");
}
}
}
Output:
Enter Main...
Enter Func1...
Entering try block...
Enter Func2...
Unknown exception caught when calling Func 2.
Exit Func1...
Exit Main...
This time the exception is not handled in Func2( )
, it is handled in Func1( )
. When Func2( )
is called, it prints the Enter
statement, and then throws an exception. Execution halts and the runtime looks for a handler, but there isn’t one. The stack unwinds, and the runtime finds a handler in Func1( )
. The catch
statement is called, and execution resumes immediately following the catch
statement, printing the Exit
statement for Func1( )
and then for Main( )
.
Make sure you are comfortable with why the Exiting Try Block
statement and the Exit Func2
statement aren’t printed. This is a classic case where putting the code into a debugger and then stepping through it can make things very clear.
So far, you’ve been working only with generic catch
statements. Best practices, however, dictate that you want, whenever possible, to create dedicated catch
statements that will handle only some exceptions and not others, based on the type of exception thrown. Example 11-4 illustrates how to specify which exception you’d like to handle.
using System; namespace SpecifyingCaughtException { public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } // try to divide two numbers // handle possible exceptions public void TestFunc( ) { try { double a = 5;double b = 0; //double b = 2; Console.WriteLine("{0} / {1} = {2}", a, b, DoDivide(a, b)); } // most derived exception type first catch (System.DivideByZeroException) { Console.WriteLine( "DivideByZeroException caught!"); } catch (System.ArithmeticException) { Console.WriteLine( "ArithmeticException caught!); } // generic exception type last Catch (Exception e) { Console.Writeline("Log: " + e.ToString( )); } } // end Test function // do the division if legal public double DoDivide(double a, double b) { if (b == 0) throw new System.DivideByZeroException( ); if (a == 0) throw new System.ArithmeticException( ); // throw new ApplicationException( ); return a / b; } } // end class } // end namespace Output: DivideByZeroException caught!
In this example, the DoDivide( )
method doesn’t let you divide 0 by another number, nor does it let you divide a number by 0. It throws an instance of DivideByZeroException
if you try to divide by 0. If you try to divide 0 by another number, there is no appropriate exception; dividing 0 by another number is a legal mathematical operation, and shouldn’t throw an exception at all. For the sake of this example, assume you don’t want 0 to be divided by any number and throw an ArithmeticException
.
When the exception is thrown, the runtime examines each exception handler in order and matches the first one it can. When you run this with a=5
and b=7
, the output is:
5 / 7 = 0.7142857142857143
As you’d expect, no exception is thrown. However, when you change the value of a
to 0
, the output is:
ArithmeticException caught!
The exception is thrown, and the runtime examines the first exception, DivideByZeroException
. Because this doesn’t match, it goes on to the next handler, ArithmeticException
, which does match.
In a final pass through, suppose you change a
to 7
and b
to 0
. This throws the DivideByZeroException
.
You have to be particularly careful with the order of the catch
statements because the DivideByZeroException
is derived from ArithmeticException
. If you reverse the catch
statements, the DivideByZeroException
matches the ArithmeticException
handler, and the exception won’t get to the DivideByZeroException
handler. In fact, if their order is reversed, it’s impossible for any exception to reach the DivideByZeroException
handler. The compiler recognizes that the DivideByZeroException
handler can’t be reached and reports a compile error!
When catching the generic exception, it is often a good idea to at least log as much about the exception as possible by calling ToString
on the exception. To see this at work, make three changes to the previous example:
Change the declared value of b
from 0
to 2
.
Uncomment the penultimate line of code.
Comment out the final line of code (as it will now be unreachable).
The output will look something like this:
Log this: System.SystemException: System error. atSpecifyingCaughtException.Test.DoDivide(Double a, Double b) in C:/.../ Specified Exception s/Program.cs:line 53
Notice that among other things, the generic exception tells you the file, the method, and the line number; this can save quite a bit of debugging time.
In some instances, throwing an exception and unwinding the stack can create a problem. For example, if you have opened a file or otherwise committed a resource, you might need an opportunity to close the file or flush the buffer.
If there is some action you must take regardless of whether an exception is thrown (such as closing a file), you have two strategies to choose from. One approach is to enclose the dangerous action in a try
block, and then to close the file in both the catch
and try
blocks. However, this is an ugly duplication of code, and it’s error-prone. C# provides a better alternative in the finally
block.
The code in the finally
block is guaranteed to be executed regardless of whether an exception is thrown. The TestFunc( )
method in Example 11-5 simulates opening a file as its first action. The method undertakes some mathematical operations, and the file is closed. It is possible that some time between opening and closing the file an exception will be thrown.
Keep the code in your finally
block simple. If an exception is thrown from within your finally
block, your finally
block will not complete.
If this were to occur, it would be possible for the file to remain open. The developer knows that no matter what happens, at the end of this method the file should be closed, so the file close function call is moved to a finally
block, where it will be executed regardless of whether an exception is thrown.
using System;
using System.Collections.Generic;
using System.Text;
namespace UsingFinally
{
public class Test
{
public static void Main( )
{
Test t = new Test( );
t.TestFunc( );
}
// try to divide two numbers
// handle possible exceptions
public void TestFunc( )
{
try
{
Console.WriteLine("Open file here");
double a = 5;
double b = 0;
Console.WriteLine("{0} / {1} = {2}",
a, b, DoDivide(a, b));
Console.WriteLine(
"This line may or may not print");
}
// most derived exception type first
catch (System.DivideByZeroException)
{
Console.WriteLine(
"DivideByZeroException caught!");
}
catch
{
Console.WriteLine("Unknown exception caught");
}finally
{
Console.WriteLine("Close file here.");
}
}
// do the division if legal
public double DoDivide(double a, double b)
{
if (b == 0)
throw new System.DivideByZeroException( );
if (a == 0)
throw new System.ArithmeticException( );
return a / b;
}
}
}
Output:
Open file here
DivideByZeroException caught!
Close file here.
Output when b = 12:
Open file here
5 / 12 = 0.416666666666667
This line may or may not print
Close file here.
In this example, one of the catch
blocks is eliminated to save space, and a finally
block is added. Whether or not an exception is thrown, the finally
block is executed (in both output examples you see the message Close file here.
).
You can create a finally
block with or without catch
blocks, but a finally
block requires a try
block to execute. It is an error to exit a finally
block with break, continue, return
, or goto
.
So far, you’ve been using the exception as a sentinel—that is, the presence of the exception signals the error—but you haven’t touched or examined the Exception
object itself. The System.Exception
object provides a number of useful methods and properties. The Message
property provides information about the exception, such as why it was thrown. The Message
property is read-only; the code throwing the exception can set the Message
property as an argument to the exception constructor.
The HelpLink
property provides a link to the help file associated with the exception. This property is read/write.
VB 6 programmers take note: in C#, you need to be careful when declaring and instantiating object variables on the same line of code. If there is a possibility that an error could be thrown in the constructor method, you might be tempted to put the variable declaration and instantiation inside the try
block. But, if you do that, the variable will only be scoped within the try
block, and it can’t be referenced within the catch
or finally
blocks. The best approach is to declare the object variable before the try
block and instantiate it within the try
block.
The StackTrace
property is read-only and is set by the runtime. In Example 11-6, the Exception.HelpLink
property is set and retrieved to provide information to the user about the DivideByZeroException
. The StackTrace
property of the exception can provide a stack trace for the error statement. A stack trace displays the call stack: the series of method calls that lead to the method in which the exception was thrown.
using System; namespace ExceptionObject { public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } // try to divide two numbers // handle possible exceptions public void TestFunc( ) { try { Console.WriteLine("Open file here"); double a = 12; double b = 0; Console.WriteLine("{0} / {1} = {2}", a, b, DoDivide(a, b)); Console.WriteLine( "This line may or may not print"); } // most derived exception type first catch (System.DivideByZeroException e) { Console.WriteLine( "DivideByZeroException!" + e); } catch (System.Exception e) { Console.WriteLine( "Log" + e.Message); } finally { Console.WriteLine("Close file here."); } } // do the division if legal public double DoDivide(double a, double b) { if (b == 0) { DivideByZeroException e = new DivideByZeroException( ); e.HelpLink = "http://www.libertyassociates.com"; throw e; } if (a == 0) throw new ArithmeticException( ); return a / b; } } } Output: Open file here DivideByZeroException! Msg: Attempted to divide by zero. HelpLink: http://www.libertyassociates.com Here's a stack trace: at ExceptionObject.Test.DoDivide(Double a, Double b) in c:...exception06.cs:line 56 at ExceptionObject.Test.TestFunc( ) in...exception06.cs:line 22 Close file here.
In the output, the stack trace lists the methods in the reverse order in which they were called; that is, it shows that the error occurred in DoDivide( )
, which was called by TestFunc( )
. When methods are deeply nested, the stack trace can help you understand the order of method calls.
In this example, rather than simply throwing a DivideByZeroException
, you create a new instance of the exception:
DivideByZeroException e = new DivideByZeroException( );
You don’t pass in a custom message, and so the default message will be printed:
DivideByZeroException! Msg:Attempted to divide by zero
.
You can modify this line of code to pass in a default message:
new DivideByZeroException( "You tried to divide by zero, which is not meaningful");
In this case, the output message will reflect the custom message:
DivideByZeroException! Msg: You tried to divide by zero, which is not meaningful
Before throwing the exception, set the HelpLink
property:
e.HelpLink = "http://www.libertyassociates.com";
When this exception is caught, the program prints the message and the HelpLink
:
catch (System.DivideByZeroException e) { Console.WriteLine(" DivideByZeroException! Msg: {0}", e.Message); Console.WriteLine(" HelpLink: {0}", e.HelpLink);
This allows you to provide useful information to the user. In addition, it prints the StackTrace
by getting the StackTrace
property of the exception object:
Console.WriteLine(" Here's a stack trace: {0} ", e.StackTrace);
The output of this call reflects a full StackTrace
leading to the moment the exception was thrown:
Here's a stack trace: at ExceptionObject.Test.DoDivide(Double a, Double b) in c:...exception06.cs:line 56 at ExceptionObject.Test.TestFunc( ) in...exception06.cs:line 22
Note that we’ve abbreviated the pathnames, so your printout might look different.
18.119.142.232