Structured Exception Handling

At the advent of an exception, the CLR evaluates the stack to determine where an exception is trapped, if at all, and how it is handled. When an exception occurs, the stack is walked in search of an appropriate exception handler. When a catch filter that matches the exception is located, the exception is handled in the related exception handler, which is the catch statement block. This sets the scope of the exception on the stack. Before the exception is handled, the CLR unwinds the stack to that scope.

Try Statements

Try blocks are observers; they watch for exceptions in the code they protect. Place only code that is prone to an exception 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 fencepost error that happens when the bounds of the array are exceeded. The result is an exception in the unprotected MethodA method. Main called MethodA, where the call site is protected in a try block. For this reason, the exception propagates from MethodA to Main, where the exception is caught and handled:

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 } catch(filter1) { handler1 } ... catch(filtern) { handlern }

    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, all catch statements should precede the finally statement. The following code demonstrates 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 Statements

A catch statement filters exceptions and determines if the exception that was raised is handled in the catch statement block. It defines whether the exception is handled at that scope or earlier in the stack. If the filter matches the exception, the exception is suppressed, and control is transferred to the adjoining catch statement block to be handled. If not, the CLR continues to search the stack for an appropriate filter. If a matching filter is not found, the exception becomes an unhandled exception.

Exceptions are derived from System.Exception. The catch filter declares an exception object and catches that exception type and any descendant. 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 statement block. System.Exception is the base class of .NET exceptions and can be used as a generic filter.

A System.Exception filter catches all exceptions. Derived classes of System.Exception catch more specific exceptions. In the previous code, the DivideByZeroException filter catches integer divide-by-zero exceptions and nothing else. A try block with multiple catch filters can catch distinct exceptions. The catches should be listed in the code from most specific first to most generic last. The following code has several exception filters, correctly ordered 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 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 exceptions, which includes divide-by-zero and any other arithmetic exceptions. The exception in the sample code is caught by the DivideByZeroException catch filter and handled in that catch statement block.

The catch filter is optional. When omitted, the default filter is catch all—which is functionally similar to System.Exception:

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 involves 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:

  • Your application has a centralized handler for the exception. There are several reasons for implementing centralized handlers, including code reuse. Instead of handling the same 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 log the exception and then propagate it to the centralized handler.

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

  • You might not want to handle the specific exception caught in a general exception filter. Propagate the unwanted exception. For example, 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 involves a single catch statement versus 12 catch statements and eliminates redundant code.

  • You want to catch an exception to gather information or to report on an exception and then propagate the exception to be further handled elsewhere. In this circumstance, you do not plan on handling the exception at that point in the call stack.

To propagate an exception, rethrow the same exception or a related exception in the catch statement block. An empty throw statement automatically propagates the caught 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 should become less specific. Exceptions from the lower echelon of an application could contain detailed information that is 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 user (or a malicious one, for that matter). 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. If necessary, record the detailed information of the original exception in a log or some other convenient and retrievable location.

When an exception is rethrown, you can preserve the original exception in the InnerException field. Successive InnerException attributes form a chain of exceptions from the current exception back to the original exception. The InnerException field 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);
                Console.WriteLine(except.InnerException.Message);
            }
        }

        public static void MethodA() {
            try {
                MethodB();
            }
            catch(DivideByZeroException inner) {

                // record divide-by-zero exception in
                //     event log.

                // inner is the inner exception
                throw new Exception("Math exception",
                    inner);
            }
        }

        public static void MethodB() {
            int var1 = 5, var2 = 0;
            var1 /= var2;
        }
    }
}

Finally Statements

The finally block is the termination handler. When an exception occurs, protected code after the exceptional event is not executed. What if it includes cleanup code? If the 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, any finally blocks within scope are called as 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 corresponding 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 uses 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 handler 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 next method on the stack is examined. 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. Try blocks can be nested within a method call or call stack. Let us assume FuncA has a try block. Within that block, FuncB is called. FuncB also has a try block. The try block in FuncB is nested by the try block in FuncA. This is the order of execution when an exception occurs:

  1. Find a 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. This defines the scope of the exception. If a matching catch filter is not found, the exception is unhandled.

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

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

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

  6. Finally blocks at the scope of the exception handler are executed.

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

Execution sequence of exception handling

Figure 12-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");
            }
        }
    }
}
..................Content has been hidden....................

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