C H A P T E R  22

Exceptions

What Are Exceptions?

An exception is a runtime error in a program that violates a system or application constraint, or a condition that is not expected to occur during normal operation. Examples are when a program tries to divide a number by zero or tries to write to a read-only file. When these occur, the system catches the error and raises an exception.

If the program has not provided code to handle the exception, the system will halt the program. For example, the following code raises an exception when it attempts to divide by zero:

   static void Main()
   {
      int x = 10, y = 0;
      x /= y;              // Attempt to divide by zero--raises an exception
   }

When this code is run, the system displays the following error message:


Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
         at Exceptions_1.Program.Main() in C:ProgsExceptionsProgram.cs:line 12

The try Statement

The try statement allows you to designate blocks of code to be guarded for exceptions and to supply code to handle them if they occur. The try statement consists of three sections, as shown in Figure 22-1.

  • The try block contains the code that is being guarded for exceptions.
  • The catch clauses section contains one or more catch clauses. These are blocks of code to handle the exceptions. They are also known as exception handlers.
  • The finally block contains code to be executed under all circumstances, whether or not an exception is raised.
Image

Figure 22-1. Structure of the try statement

Handling the Exception

The previous example showed that attempting to divide by zero causes an exception. You can modify the program to handle that exception by placing the code inside a try block and supplying a simple catch clause. When the exception is raised, it is caught and handled in the catch block.

   static void Main()
   {
      int x = 10;
   
      try
      {
         int y = 0;
         x /= y;                    // Raises an exception
      }
      catch
      {
            ...                     // Code to handle the exception
   
         Console.WriteLine("Handling all exceptions - Keep on Running");
      }
   }

This code produces the following message. Notice that, other than the output message, there is no indication that an exception has occurred.


Handling all exceptions - Keep on Running

The Exception Classes

There are many different types of exceptions that can occur in a program. The BCL defines a number of exception classes, each representing a specific type. When one occurs, the CLR does the following:

  • It creates an exception object for the type.
  • It looks for an appropriate catch clause to handle it.

All exception classes are ultimately derived from the System.Exception class. Figure 22-2 shows a portion of the exception inheritance hierarchy.

Image

Figure 22-2. Structure of the exception hierarchy

An exception object contains read-only properties with information about the exception that caused it. Table 22-1 shows some of these properties.

Image

The catch Clause

The catch clause handles exceptions. There are three forms, allowing different levels of processing. Figure 22-3 shows the forms.

Image

Figure 22-3. The three forms of the catch clause

The general catch clause can accept any exception but can’t determine the type of exception that caused it. This allows only general processing and cleanup for whatever exception might occur.

The specific catch clause form takes the name of an exception class as a parameter. It matches exceptions of the specified class or exception classes derived from it.

The specific catch clause with object form gives you the most information about the exception. It matches exceptions of the specified class, or exception classes derived from it. It gives you a reference to the exception object created by the CLR by assigning it to the exception variable. You can access the exception variable’s properties within the block of the catch clause to get specific information about the exception raised.

For example, the following code handles exceptions of type IndexOutOfRangeException. When one occurs, a reference to the actual exception object is passed into the code with parameter name e. The three WriteLine statements each read a string field from the exception object.

                  Exception type    Exception variable
                                     
    catch ( IndexOutOfRangeException  e )
   {                                 Accessing the exception variables
                                                
       Console.WriteLine( "Message: {0}", e.Message );
       Console.WriteLine( "Source:  {0}", e.Source );
       Console.WriteLine( "Stack:   {0}", e.StackTrace );

Examples Using Specific catch Clauses

Going back to our divide-by-zero example, the following code modifies the previous catch clause to specifically handle exceptions of the DivideByZeroException class. While in the previous example, the catch clause would handle any exception raised in the try block, the current example will only handle those of the DivideByZeroException class.

   int x = 10;
   try
   {
      int y = 0;
      x /= y;                    // Raises an exception
   }            Exception type
                                         
   catch ( DivideByZeroException )
   {
         ...
      Console.WriteLine("Handling an exception.");
   }

You could further modify the catch clause to use an exception variable. This allows you to access the exception object inside the catch block.

   int x = 10;
   try
   {
      int y = 0;
      x /= y;                    // Raises an exception
   }          Exception type     Exception variable
                                 
   catch ( DivideByZeroException  e )
   {                             Accessing the exception variables
                                              
      Console.WriteLine("Message: {0}", e.Message );
      Console.WriteLine("Source:  {0}", e.Source );
      Console.WriteLine("Stack:   {0}", e.StackTrace );
   }

On my computer, this code produces the following output. On your machine, the file path in the third and fourth lines will be different, and will match the location of your project and solution directories.


Message: Attempted to divide by zero.
Source:  Exceptions 1
Stack:      at Exceptions_1.Program.Main() in C:ProgsExceptions 1
Exceptions 1Program.cs:line 14

The catch Clauses Section

The purpose of a catch clause is to allow you to handle an exception in an elegant way. If your catch clause is of the form that takes a parameter, then the system has set the exception variable to a reference to the exception object, which you can inspect to determine the cause of the exception. If the exception was the result of a previous exception, you can get a reference to that previous exception’s object from the variable’s InnerException property.

The catch clauses section can contain multiple catch clauses. Figure 22-4 shows a summary of the catch clauses section.

Image

Figure 22-4. Structure of the catch clauses section of a try statement

When an exception is raised, the system searches the list of catch clauses in order, and the first catch clause that matches the type of the exception object is executed. Because of this, there are two important rules in ordering the catch clauses. They are the following:

  • The specific catch clauses must be ordered with the most specific exception types first, progressing to the most general. For example, if you declare an exception class derived from NullReferenceException, the catch clause for your derived exception type should be listed before the catch clause for NullReferenceException.
  • If there is a general catch clause, it must be last, after all specific catch clauses. Using the general catch clause is discouraged because it can hide bugs by allowing the program to continue execution when your code should be handling the error in a specific way. It can also leave the program in an unknown state. Therefore, you should use one of the specific catch clauses if at all possible.

The finally Block

If a program’s flow of control enters a try statement that has a finally block, the finally block is always executed. Figure 22-5 shows the flow of control.

  • If no exception occurs inside the try block, then at the end of the try block, control skips over any catch clauses and goes to the finally block.
  • If an exception occurs inside the try block, then the appropriate catch clause in the catch clauses section is executed, followed by execution of the finally block.
Image

Figure 22-5. Execution of the finally block

The finally block will always be executed before returning to the calling code, even if a try block has a return statement or an exception is thrown in the catch block. For example, in the following code, there is a return statement in the middle of the try block that is executed under certain conditions. This does not allow it to bypass the finally statement.

   try
   {
      if (inVal < 10) {
         Console.Write("First Branch  - ");
         return;
      }
      else
         Console.Write("Second Branch - ");
   }
   finally
   { Console.WriteLine("In finally statement"); }

This code produces the following output when variable inVal has the value 5:


First Branch  - In finally statement

Finding a Handler for an Exception

When a program raises an exception, the system checks to see whether the program has provided a handler for it. Figure 22-6 shows the flow of control.

  • If the exception occurred inside a try block, the system will check to see whether any of the catch clauses can handle the exception.
  • If an appropriate catch clause is found, then the following happens:
    • The catch clause is executed.
    • If there is a finally block, it is executed.
    • Execution continues after the end of the try statement (that is, after the finally block, or after the last catch clause if there is no finally block).
Image

Figure 22-6. Exception with handler in current try statement

Searching Further

If the exception was raised in a section of code that was not guarded by a try statement, or if the try statement does not have a matching exception handler, the system will have to look further for a matching handler. It will do this by searching down the call stack, in sequence, to see whether there is an enclosing try block with a matching handler.

Figure 22-7 illustrates the search process. On the left of the figure is the calling structure of the code, and on the right is the call stack. The figure shows that Method2 is called from inside the try block of Method1. If an exception occurs inside the try block in Method2, the system does the following:

  • First, it checks to see whether Method2 has exception handlers that can handle the exception.
    • If so, Method2 handles it, and program execution continues.
    • If not, the system continues down the call stack to Method1, searching for an appropriate handler.
  • If Method1 has an appropriate catch clause, the system does the following:
    • It goes back to the top of the call stack—which is Method2.
    • It executes Method2’s finally block and pops Method2 off the stack.
    • It executes Method1’s catch clause and its finally block.
  • If Method1 doesn’t have an appropriate catch clause, the system continues searching down the call stack.
Image

Figure 22-7. Searching down the call stack

General Algorithm

Figure 22-8 shows the general algorithm for handling an exception.

Image

Figure 22-8. The general algorithm for handling an exception

Example of Searching Down the Call Stack

In the following code, Main starts execution and calls method A, which calls method B. A description and diagram of the process are given after the code and in Figure 22-9.

   class Program
   {
      static void Main()
      {
         MyClass MCls = new MyClass();
         try
            { MCls.A(); }
         catch (DivideByZeroException e)
            { Console.WriteLine("catch clause in Main()"); }
         finally
            { Console.WriteLine("finally clause in Main()"); }
         Console.WriteLine("After try statement in Main.");
         Console.WriteLine("            -- Keep running.");
       }
   }
   
   class MyClass
   {
      public void A()
      {
         try
            { B(); }
         catch (System.NullReferenceException)
            { Console.WriteLine("catch clause in A()"); }
         finally
            { Console.WriteLine("finally clause in A()"); }
      }
   
      void B()
      {
         int x = 10, y = 0;
         try
            { x /= y; }
         catch (System.IndexOutOfRangeException)
            { Console.WriteLine("catch clause in B()"); }
         finally
            { Console.WriteLine("finally clause in B()"); }
      }
   }

This code produces the following output:


finally clause in B()
finally clause in A()
catch clause in Main()
finally clause in Main()
After try statement in Main.
            -- Keep running.

  1. Main calls A, which calls B, which encounters a DivideByZeroException exception.
  2. The system checks B’s catch section for a matching catch clause. Although it has one for IndexOutOfRangeException, it doesn’t have one for DivideByZeroException.
  3. The system then moves down the call stack and checks A’s catch section, where it finds that A also doesn’t have a matching catch clause.
  4. The system continues down the call stack and checks Main’s catch clause section, where it finds that Main does have a DivideByZeroException catch clause.
  5. Although the matching catch clause has now been located, it is not executed yet. Instead, the system goes back to the top of the stack, executes B’s finally clause, and pops B from the call stack.
  6. The system then moves to A, executes its finally clause, and pops A from the call stack.
  7. Finally, Main’s matching catch clause is executed, followed by its finally clause. Execution then continues after the end of Main’s try statement.
Image

Figure 22-9. Searching the stack for an exception handler

Throwing Exceptions

You can make your code explicitly raise an exception by using the throw statement. The syntax for the throw statement is the following:

   throw ExceptionObject;

For example, the following code defines a method called PrintArg, which takes a string argument and prints it out. Inside the try block, it first checks to make sure the argument is not null. If it is, it creates an ArgumentNullException instance and throws it. The exception instance is caught in the catch statement, and the error message is printed. Main calls the method twice: once with a null argument and then with a valid argument.

   class MyClass
   {
      public static void PrintArg(string arg)
      {
         try
         {
            if (arg == null)                              Supply name of null argument.
            {                                                         
               ArgumentNullException myEx = new ArgumentNullException("arg");
               throw myEx;
            }
            Console.WriteLine(arg);
         }
         catch (ArgumentNullException e)
         {
            Console.WriteLine("Message:  {0}", e.Message);
         }
      }
   }
   class Program
   {
      static void Main()
      {
         string s = null;
         MyClass.PrintArg(s);
         MyClass.PrintArg("Hi there!");
      }
   }

This code produces the following output:


Message:  Value cannot be null.
Parameter name: arg
Hi there!

Throwing Without an Exception Object

The throw statement can also be used without an exception object, inside a catch block.

  • This form rethrows the current exception, and the system continues its search for additional handlers for it.
  • This form can be used only inside a catch statement.

For example, the following code rethrows the exception from inside the first catch clause:

   class MyClass
   {
      public static void PrintArg(string arg)
      {
         try
         {
            try
            {
               if (arg == null)                           Supply name of null argument.
               {                                                          
                  ArgumentNullException myEx = new ArgumentNullException("arg");
                  throw myEx;
               }
               Console.WriteLine(arg);
            }
            catch (ArgumentNullException e)
            {
               Console.WriteLine("Inner Catch:  {0}", e.Message);
               throw;
            }    
         }    Rethrow the exception, with no additional parameters.
         catch
         {
            Console.WriteLine("Outer Catch:  Handling an Exception.");
         }
      }
   }
   
   class Program {
      static void Main() {
         string s = null;
         MyClass.PrintArg(s);
      }
   }

This code produces the following output:


Inner Catch:  Value cannot be null.
Parameter name: arg
Outer Catch:  Handling an Exception.

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

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