Chapter 11. Handling Exceptions

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.

Throwing and Catching 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.

Tip

C++ programmers take note: in C#, not just any object can be thrown—it must be derived from System.Exception.

The throw Statement

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.

Unhandled exception

Figure 11-1. Unhandled exception

If you click View Detail, you find the details of the unhandled exception, as shown in Figure 11-2.

Exception details

Figure 11-2. Exception details

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.

The catch Statement

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.

Taking corrective action

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.

Unwinding the call stack

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.

Creating dedicated catch statements

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.

Tip

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.

The finally Statement

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.

Tip

A finally block can be created 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.

Exception Objects

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.

Tip

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.

Custom Exceptions

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.

Tip

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.

Rethrowing Exceptions

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.

Tip

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.11