Like many object-oriented languages, C# handles errors and 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.
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 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.
Example 11-1. Throwing an exception
namespace Programming_CSharp { using System; 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.Exception( ); 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 stops, and the CLR looks to see if 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.
Example 11-2. Catching an exception
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace CatchingAnException { public classTest { 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.Exception( ); Console.WriteLine( "Exiting try block..." ); } catch { 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. You would
typically put the try
block around a potentially
“dangerous” statement, such as
accessing a file, allocating large blocks of memory, etc.
Following the try
statement is a generic
catch
statement. The catch
statement in Example 11-2 is generic because you
haven’t specified what kind of exceptions to catch.
In this case, the statement will catch any exceptions that are
thrown. Using catch
statements to catch specific
types of exceptions is discussed later in this chapter.
In Example 11-2, the
catch
statement simply reports that the
exception has been caught and handled. In a real-world example, you
might take corrective action to fix the problem that caused an
exception to be thrown. 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 print an error message so the user knows what went wrong.
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, execution halts immediately and 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.
Example 11-3. Catch in a calling function
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace CatchingExceptionInCallingFunc { public classTest { 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( "Exception caught and handled." ); } Console.WriteLine( "Exit Func1..." ); } public void Func2( ) { Console.WriteLine( "Enter Func2..." ); throw new System.Exception( ); Console.WriteLine( "Exit Func2..." ); } } } Output: Enter Main... Enter Func1... Entering try block... Enter Func2... Exception caught and handled. 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. You
can create dedicated catch
statements that 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.
Example 11-4. Specifying the exception to catch
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace SpecifyingCaughtException { public classTest { 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; 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 { Console.WriteLine( "Unknown exception caught" ); } } // 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: 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
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!
It is possible to distribute your
try
/catch
statements, catching
some specific exceptions in one function and more generic exceptions
in higher, calling functions. Your design goals should dictate the
exact design.
Assume you have a method A that calls another method B, which in turn
calls method C. Method C calls method D, which then calls method E.
Method E is deep in your code; methods B and A are higher up. If you
anticipate that method E might throw an exception, you should create
a try
/catch
block deep in your
code to catch that exception as close as possible to the place where
the problem arises. You might also want to create more general
exception handlers higher up in the code in case unanticipated
exceptions slip by.
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.
In the event, however, that 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.[1] 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. 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.
Example 11-5. Using a finally block
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace UsingFinally { public classTest { 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, and in both output
examples you see the message Close
file
here
.
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.
VB6 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.
Example 11-6. Working with an exception object
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace ExceptionObject { public classTest { 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! Msg: {0}", e.Message ); Console.WriteLine( " HelpLink: {0}", e.HelpLink ); Console.WriteLine( " Here's a stack trace: {0} ", e.StackTrace ); } catch (System.Exception e) { Console.WriteLine( "Unknown exception caught" + 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 Programming_CSharp.Test.DoDivide(Double a, Double b) in c:...exception06.cs:line 56 at Programming_CSharp.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 Programming_CSharp.Test.DoDivide(Double a, Double b) in c:...exception06.cs:line 56 at Programming_CSharp.Test.TestFunc( ) in...exception06.cs:line 22
Note that I’ve abbreviated the pathnames, so your printout might look different.
The intrinsic exception types the CLR
provides, coupled with the custom messages shown in the previous
example, will often be all you need to provide extensive information
to a catch
block when an exception is thrown.
There will be times, however, when you will want to have separate
exception handlers based on what caused the exception. To do so, you
will want to create your own custom exception types (and thus, you
can create specialized handlers). Your custom exception types can add
additional information or capabilities, but often their principle
reason for existing is just to be a different type that the
catch
block can differentiate.
Microsoft recommends that you never throw a base
Exception
or even an
ApplicationException
object; it is best to treat
these as abstract types.
It is a simple matter to create your own custom
exception class; the only restriction is that it
must derive (directly or indirectly) from
System.ApplicationException
. Example 11-7 illustrates the creation of a custom
exception.
Example 11-7. Creating a custom exception
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace CustomExceptions { public classMyCustomException : System.ApplicationException { public MyCustomException( string message ): base(message) { } } 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 = 0; double b = 5; 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! Msg: {0}", e.Message ); Console.WriteLine( " HelpLink: {0} ", e.HelpLink ); } catch ( MyCustomException e ) { Console.WriteLine( " MyCustomException! Msg: {0}", e.Message ); Console.WriteLine( " HelpLink: {0} ", e.HelpLink ); } 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 ) { DivideByZeroException e = new DivideByZeroException( ); e.HelpLink = "http://www.libertyassociates.com"; throw e; } if ( a == 0 ) { MyCustomException e = new MyCustomException( "Can't have zero divisor" ); e.HelpLink = "http://www.libertyassociates.com/NoZeroDivisor.htm"; throw e; } return a / b; } } }
MyCustomException
is derived from
System.ApplicationException
and consists of
nothing more than a constructor that takes a string
message
that it passes to its base class, as
described in Chapter 4. In this case, the
advantage of creating this custom exception class is that it better
reflects the particular design of the Test
class,
in which it is not legal to have a zero divisor. Using the
ArithmeticException
rather than a custom exception
would work as well, but it might confuse other programmers because a
zero divisor wouldn’t normally be considered an
arithmetic error.
You might want your
catch
block to take some initial corrective action
and then rethrow the exception to an outer try
block (in a calling function). It might rethrow the
same exception, or it might throw a different
one. If it throws a different one, it may want to embed the original
exception inside the new one so that the calling method can
understand the exception history. The
InnerException
property of the new exception retrieves the original exception.
Some exceptions make any sense only in the context in which they were
thrown. This is particularly the case with, for example, the
NullReferenceException
,
which may result from bad user input. In cases where you
can’t anticipate this by checking input in advance,
you should catch the exception, and rethrow an
ArgumentException
to provide the caller with a better indication of the cause of the
problem.
It can sometimes be a good idea to put a catch handler at the
boundary of a component or design layer, to trap unexpected
exceptions. In this case you might throw a custom
InternalErrorException
signaling the client code that something went wrong within your
component.
Because the InnerException
is also an exception, it too might have an inner exception. Thus, an
entire chain of exceptions can be
nested
one within the other, much like Matryoshka
dolls[2] are contained one
within the other. Example 11-8 illustrates.
Example 11-8. Rethrowing inner exceptions
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace RethrowingExceptions { public class MyCustomException : System.ApplicationException { public MyCustomException( string message,Exception inner ): base(message,inner) { } } public class Test { public static void Main( ) { Test t = new Test( ); t.TestFunc( ); } public void TestFunc( ) { try { DangerousFunc1( ); } // if you catch a custom exception // print the exception history catch ( MyCustomException e ) { Console.WriteLine( " {0}", e.Message ); Console.WriteLine( "Retrieving exception history..." ); Exception inner = e.InnerException; while ( inner != null ) { Console.WriteLine( "{0}", inner.Message ); inner = inner.InnerException; } } } public void DangerousFunc1( ) { try { DangerousFunc2( ); } // if you catch any exception here // throw a custom exception catch ( System.Exception e ) { MyCustomException ex = new MyCustomException( "E3 - Custom Exception Situation!", e ); throw ex; } } public void DangerousFunc2( ) { try { DangerousFunc3( ); } // if you catch a DivideByZeroException take some // corrective action and then throw a general exception catch ( System.DivideByZeroException e ) { Exception ex = new Exception( "E2 - Func2 caught divide by zero", e ); throw ex; } } public void DangerousFunc3( ) { try { DangerousFunc4( ); } catch ( System.ArithmeticException ) { Console.WriteLine("Arithmetic exception caught in DF3, and rethrown..."); throw; } catch ( System.Exception ) { Console.WriteLine( "Exception handled here." ); } } public void DangerousFunc4( ) { throw new DivideByZeroException( "E1 - DivideByZero Exception" ); } } } Output: E3 - Custom Exception Situation! Retrieving exception history... E2 - Func2 caught divide by zero E1 - DivideByZeroException
Because this code has been stripped to the essentials, the output might leave you scratching your head. The best way to see how this code works is to use the debugger to step through it.
Begin by calling DangerousFunc1()
in a
try
block:
try { DangerousFunc1( ); }
DangerousFunc1( )
calls
DangerousFunc2()
, which calls
DangerousFunc3( )
, which in turn calls
DangerousFunc4()
. All these calls are in their own
try
blocks. At the end,
DangerousFunc4()
throws a
DivideByZeroException
.
System.DivideByZeroException
normally has its own
error message, but you are free to pass in a custom message. Here, to
make it easier to identify the sequence of events, the custom message
E1
-
DivideByZeroException
is passed in.
The exception thrown in DangerousFunc4()
is caught
in the catch
block in
DangerousFunc3()
. The logic in
DangerousFunc3( )
is that if any
ArithmeticException
is caught (such as
DivideByZeroException
), it takes no action; it
just rethrows the exception:
catch (System.ArithmeticException)
{Console.WriteLine("Arithmetic exception caught in DF3,
and rethrown...");
throw;
}
The syntax to rethrow the exact same exception (without modifying it)
is just the word
throw
.
The exception is thus rethrown to
DangerousFunc2()
, which catches it, takes some
corrective action, and throws a new exception of type
Exception
. In the constructor to that new
exception, DangerousFunc2()
passes in a custom
message (E2
-
Func2
caught
divide
by
zero
) and
the
original
exception. Thus, the original exception
(E1
) becomes the InnerException
for the new exception (E2
).
DangerousFunc2( )
then throws this new
E2
exception to
DangerousFunc1( )
.
DangerousFunc1()
catches the exception, does some
work, and creates a new exception of type
MyCustomException
. It passes a new string
(E3
-
Custom
Exception
Situation!
) to the
constructor as well as the exception it just caught
(E2
). Remember, the exception it just caught is
the exception with a DivideByZeroException
(E1)
as its inner exception. At this point, you
have an exception of type MyCustomException
(E3)
, with an inner exception of type
Exception
(E2)
, which in turn
has an inner exception of type
DivideByZeroException
(E1)
. All
this is then thrown to the test
function, where it
is caught.
When the catch
function runs, it prints the
message:
E3 - Custom Exception Situation!
and then drills down through the layers of inner exceptions, printing their messages:
while (inner != null) { Console.WriteLine("{0}",inner.Message); inner = inner.InnerException; }
The output reflects the chain of exceptions thrown and caught:
Retrieving exception history... E2 - Func2 caught divide by zero E1 - DivideByZero Exception
As an alternative, you can call ToString( )
on the
exception:
Console.Write(e.ToString());
The output reflects the entire stack of messages and the call stacks associated with them:
RethrowingExceptions.MyCustomException: E3 - Custom Exception Situation! ---> System.Exception: E2 - Func2 caught divide by zero ---> System.DivideByZeroException: E1 - DivideByZero Exception at RethrowingExceptions.Test.DangerousFunc4() in c: ethrowingexceptions rethrowingexceptions.cs:line 114 at RethrowingExceptions.Test.DangerousFunc3( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 102 at RethrowingExceptions.Test.DangerousFunc2( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 79 --- End of inner exception stack trace --- at RethrowingExceptions.Test.DangerousFunc2( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 89 at RethrowingExceptions.Test.DangerousFunc1( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 61 --- End of inner exception stack trace --- at RethrowingExceptions.Test.DangerousFunc1( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 71 at RethrowingExceptions.Test.TestFunc( ) in c: ethrowingexceptions rethrowingexceptions.cs:line 33
[1] If you throw an exception from within your
finally
block, there is no guarantee that your
finally
block will complete execution.
[2] In earlier editions I referred to nested Ukrainian dolls. Many readers have written to say that they are Russian dolls. The dolls I refer to are properly called Matryoshka dolls and, according to Internet sources, are associated with both Russia and the Ukraine. As an interesting additional note, there is evidence of nested dolls as far back as 11th century China.
3.145.7.208