Structured Exception Handling


A structured exception evaluates the stack to determine when code is protected and where an exception is caught and handled. When exceptions occur, the stack is walked in search of an exception handler. When a matching catch filter is located, the exception is handled in the exception handler, which is the catch block. This sets the scope of the exception on the stack. After the exception is handled, the CLR unwinds the stack to that scope.

Try Statement

Try blocks are observers; they watch for exceptions in protected code. Place code prone to raising exceptions in a try block. Do not attempt to protect an entire application in a try block—there are more convenient and practical means of accomplishing the same feat, as described later in this chapter.

As mentioned, exceptions are stack-based. The following code contains a fence-post error that happens when the bounds of the array are exceeded. The result is an exception in the unprotected MethodA method. Main calls MethodA, where the scope of Main encompasses MethodA.

For this reason, the try block in Main extends protection to MethodA. The exception is trapped in Main.

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             try {                 MethodA();             }             catch(Exception except) {                  Console.WriteLine(except.Message);             }         }         public static void MethodA() {             int [] values={1,2,3,4};             for(int count=0; count<=values.Length; ++count) {                 Console.WriteLine(values[count]);             }         }     } } 

This is the syntax of the try statement:

  • try { protected} catch1(filter1) { handler1} catchn(filter n) { handler n} finally {terminationhandler}

Try statements must be paired with either a catch statement or a finally statement. There can be zero or more catch statements attached to a try statement; there are zero or one finally statements. If both catch and finally are present, the catch statement should precede the finally statement. The following code has various combinations of try, catch, and finally statements:

 // try..catch try { } catch(Exception e) { } // try..finally try { } finally{ } // try..catch..finally try { } catch(Exception e) { } finally{ } // try..catches..finally try { } catch(Exception e) { } catch { } finally{ } 

Catch Statement

Catch statements filter and handle exceptions. When an exception is raised, the filter sets which exceptions are handled at that scope in the stack. If the filter matches the exception, the exception is suppressed, and control is transferred to the adjoining catch block to be handled. The CLR searches the stack for an appropriate filter. If a matching filter is not found, the exception becomes an unhandled exception.

Exceptions must be derived from System.Exception. The catch filter defines an exception type and catches exception of that type or any descendants. The exception object contains details of the exception, such as a user-friendly message describing the exception. The exception object caught in the filter is accessible only in the catch block. System.Exception is the base class of .NET exceptions and the generic filter.

A System.Exception filter catches all managed exceptions. Derived classes of System.Exception catch more-specific exceptions. In the previous code, the DivideByZeroException filter caught integer divide by zero exceptions and nothing else. A try block can be appended to multiple catches for catching distinct exceptions. The catches are ordered from specific to generic. The following code has several exception filters, from specific to generic:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){              try {                  int var1=5, var2=0;                  var1/=var2;              }              catch(DivideByZeroException except) {                  Console.WriteLine("Exception "+except.Message);              }              catch(System.ArithmeticException except) {                  Console.WriteLine("Exception "+except.Message);              }              catch(Exception except) {                  Console.WriteLine("Exception "+except.Message);              }         }     } } 

In the preceding code, DivideByZeroException is very specific and catches only divide by zero exceptions. ArithmeticException is less specific and catches a variety of arithmetic exceptions, including the divide by zero exception. Exception catches all managed exceptions, which includes divide by zero and arithmetic exceptions. In the preceding code, the exception is caught at the DivideByZeroException catch handler.

The catch filter is optional, and the default is catch all. Although System.Exception catches any managed exceptions, catch all catches both managed and unmanaged exceptions. In most circumstances, native exceptions are mapped to managed exceptions, which are thrown in the RaiseTheException native function. RaiseTheException uses the RaiseException API to construct the managed exception. Exceptions not mapping to managed exceptions are outside this normal mechanism. A catch with no catch filter is a catch all. This code has a catch all:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){              try {                 int var1=5, var2=0;                 var1/=var2;              }              catch(DivideByZeroException except) {                 Console.WriteLine("Exception "+except.StackTrace);              }              catch {                 // catch remaining managed and unmanaged exceptions              }         }     } } 

Propagating Exceptions

Exceptions are not always handled locally where the exception is caught. It is sometimes beneficial to catch an exception and then propagate the exception. Propagating an exception is catching and then rethrowing the exception. Rethrowing an exception continues the search along the call stack to find an appropriate handler. Here are some reasons to propagate an exception:

  • There is a centralized handler for the exception. There are several reasons for implementing centralized handlers, including code reuse. Instead of handling an exception in various locations in an application, concentrate code for certain exceptions in a centralized handler. Wherever the exception is raised, the proper response is to record the exception and then delegate to the centralized handler. A central handler can be used to handle all exceptions in a single place.

  • Resources required to handle the exception are not available locally. For example, an exception is raised because of an invalid database connection. However, the correct connection string is read from a file not available where the exception occurs. The solution is to propagate the exception to a handler that has access to the file resource.

  • Propagate unwanted exceptions caught in the umbrella of the exception filter. This would be useful for catching all DataException types with the exception of the DuplicateNameException. One solution would be to write 12 individual catches—one for each of the data exceptions except for the DuplicateNameException exception. A better solution is to catch the DataException type and propagate the DuplicateNameException when necessary. This is one catch statement versus 12 catch statements and eliminates redundant code.

  • Catch an exception to gather information or to report on an exception, and then propagate the exception.

To propagate an exception, rethrow the same exception or another exception in the catch block. An empty throw statement propagates the caught exception. Alternatively, throw a different exception.

Exceptions might propagate through several layers of an application. Ultimately, the exception could percolate to the user interface level. As an exception percolates, the exception becomes less specific. Exceptions from the lower echelon of an application contain detailed information appropriate to the application developer, but probably not relevant to the user. Internal exceptions might contain security and other sensitive information not appropriate for a benign (or malicious) user. Record the facts of the internal exception if logging is preferable. Exceptions that reach the user should present user-relevant information: a user-friendly message, steps to resolve the exception, or even a customer support link.

When an original exception is rethrown, you can preserve the that exception in the InnerException attribute. Successive InnerException attributes form a chain of exceptions from the current exception to the original exception. The InnerException can be initialized in the constructor of the new exception. Here is sample code that propagates an exception and sets the inner exception:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             try {                 MethodA();             }             catch(Exception except) {                 Console.WriteLine(except.Message);             }         }         public static void MethodA() {             try {                 MethodB();             }             catch(DivideByZeroException inner) {                 // record divide by zero exception in                 //     event log.                 // except is inner exception                 throw new Exception("Math exception",                     inner);             }         }         public static void MethodB() {             int var1=5, var2=0;             var1/=var2;         }     } } 

Finally Statement

The finally block is the termination handler. When an exception is raised, protected code after the infraction is not executed. What if it is cleanup code? If cleanup code is not executed, resources are left dangling. Termination handlers are the solution. Code that must execute, regardless of an exception occurring, is placed in a termination handler. When an exception is raised, the finally blocks within scope are called before the stack is unwound. Note that the termination handler is executed even when there is no occurrence of an exception. Execution simply falls through the try block into the attached finally block. In the termination handler, you could close a file, release a database connection, or otherwise manage resources.

Here is a typical termination handler:

 using System; using System.IO; namespace Donis.CSharpBook{     public class FileWriter{         public static void Main(){             StreamWriter sw=null;             try {                 sw=new StreamWriter("date.txt");                 sw.Write(DateTime.Now.ToLongTimeString());                 throw new ApplicationException("exception");                 // dangling code             }             finally {                 sw.Close();                 Console.WriteLine("file closed");             }         }     } } 

Exception Information Table

The CLR refers to the Exception Information Table to track protected code in an efficient manner. Because of the Exception Information Table, there is no overhead associated with an exception unless an exception occurs.

An Exception Information Table is constructed for every managed application. The table has an entry for each method in the program. Each entry is an array, where the array elements describe the filters and handlers of that method. Entries in the array represent a catch filter, user-filtered handler, catch handler, or termination handler. User-filtered handlers use the when clause and are available in Visual Basic .NET, but not in C#.

When an exception occurs, the CLR consults the Exception Information Table. The entry for the method hosting the exception is searched for a filter that matches the exception. If the array is empty or a matching filter is not found, the entry of the outer method is examined next. When the boundary of the application is reached, the exception is considered unhandled.

Nested Try Blocks

Try blocks can be nested. The order of evaluation is more complex with nested try blocks. This is the order of execution when an exception occurs:

  1. The first step is to find an appropriate try block. If an exception is raised outside a protected block, the exception is not trapped and is therefore unhandled.

  2. From the try block, walk the stack until a matching catch filter is found. If a matching filter is not found, the exception is unhandled. This defines the scope of the exception.

  3. Before the stack is unwound, finally blocks within the scope of the exception are run. The innermost finally blocks are executed first.

  4. The catch block of the filter is executed as the exception handler.

  5. Execution continues at the first statement after the catch block.

  6. Finally blocks at scope of exception are executed.

Figure 9-1 diagrams the sequence when an exception is raised in a nested try block.

image from book
Figure 9-1: Execution sequence of exception handling

The following code has several nested try blocks:

 using System; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){              try {                 Console.WriteLine("outer - try");                 try {                     Console.WriteLine("inner - try");                     throw new ApplicationException("exception");                 }                 finally {                     Console.WriteLine("inner - finally");                 }             }             catch(Exception except){                 Console.WriteLine("outer - catch");             }             finally {                 Console.WriteLine("outer - finally");             }         }     } } 




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net