10. Exception Handling

Chapter 4 discussed using the try/catch/finally blocks for standard exception handling. In that chapter, the catch block always caught exceptions of type System.Exception. This chapter defines some additional details of exception handling—specifically, details surrounding additional exception types, defining custom exceptions, and multiple catch blocks for handling each type. This chapter also details exceptions because of their reliance on inheritance.

Image

Multiple Exception Types

Listing 10.1 throws a System.ArgumentException, not the System.Exception type demonstrated in Chapter 4. C# allows code to throw any type that derives (perhaps indirectly) from System.Exception.

The code for throwing any exception is simply to prefix the exception instance with the keyword throw. The type of exception used is obviously the type that best describes the circumstances surrounding the error that caused the exception.

For example, consider the TextNumberParser.Parse() method in Listing 10.1.

Listing 10.1. Throwing an Exception


public sealed class TextNumberParser
{
  public static int Parse(string textDigit)
  {
      string[] digitTexts =
          { "zero", "one", "two", "three", "four",
              "five", "six", "seven", "eight", "nine" };

      int result = Array.IndexOf(
          digitTexts, textDigit.ToLower());

      if (result < 0)
      {
          throw new ArgumentException(
              "The argument did not represent a digit",
              "textDigit");

      }

      return result;
  }
}

Instead of throwing System.Exception, it is more appropriate to throw ArgumentException because the type itself indicates what went wrong and includes special parameters for identifying which parameter was at fault.

Two similar exceptions are ArgumentNullException and NullReferenceException. ArgumentNullException should be thrown for the inappropriate passing of null arguments. This is a special case of an invalid parameter exception that would more generally (when it wasn’t null) be thrown as an ArgumentException or an ArgumentOutOfRangeException. NullReferenceException is generally something that only the underlying runtime will throw with an attempt to dereference a null value—to call a member on an object whose value is null. Instead of causing a NullReferenceException, programmers should check parameters for null before accessing them and then throw an ArgumentNullException, which can provide more contextual information such as the parameter name.

There are several other exceptions that are only intended for the runtime and that derive (sometimes indirectly) from System.SystemException. They include System.StackOverflowException, System.OutOfMemoryException, System.Runtime.InteropServices.COMException, System.ExecutionEngineException, and System.Runtime.InteropServices.SEHException. Do not throw exceptions of these types. Similarly, avoid throwing a System.Exception or System.ApplicationException, as these are so general that they provide little indication of the cause of or resolution to the problem. Instead, throw the most derived exception that fits the scenario. Obviously, developers should avoid creating APIs that could potentially result in a system failure. However, if the executing code reaches a certain state such that continuing to execute is unsafe or unrecoverable, call System.Environemnt.FailFast(). This will immediately terminate the process after writing a message to the Windows Application event log, and will even include the message as part of Windows Error Reporting if the user so chooses.


Guidelines

DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type (ArgumentNullException, for example), if applicable.

DO set the ParamName property when throwing an ArgumentException or one of the subclasses.

DO throw the most specific (most derived) exception that makes sense.

DO NOT throw a NullReferenceException. Instead, throw ArgumentNullException when a value is unexpectedly null.

DO NOT throw a System.SystemException or an exception type that derives from it.

DO NOT throw a System.Exception or System.ApplicationException.

CONSIDER terminating the process by calling System.Environment.FailFast() if the program encounters a scenario where it is unsafe for further execution.


Catching Exceptions

Throwing a particular exception type enables the catcher to use the exception’s type itself to identify the problem. It is not necessary, in other words, to catch the exception and use a switch statement on the exception message to determine what action to take in light of the exception. Instead, C# allows for multiple catch blocks, each targeting a specific exception type, as Listing 10.2 shows.

Listing 10.2. Catching Different Exception Types


using System;

public sealed class Program
{
  public static void Main(string[] args)
  {
      try
      {
              // ...
              throw new InvalidOperationException(
                  "Arbitrary exception");
              // ...
      }
      catch (NullReferenceException exception)
      {
          // Handle NullReferenceException
      }
      catch (ArgumentException exception)
      {
          // Handle ArgumentException
      }
      catch (InvalidOperationException exception)
      {
         bool exceptionHandled=false;
          // Handle InvalidOperationException
         // ...
          if(!exceptionHandled)
          {
               throw;
          }
       }
      catch (SystemException)
      {
          // Handle SystemException
      }
      catch (Exception exception)
      {
          // Handle Exception
      }
      finally
      {
          // Handle any cleanup code here as it runs
          // regardless of an exception or not.
      }
  }
}

Listing 10.2 has five catch blocks, each handling a different type of exception. When an exception occurs, the execution will jump to the catch block with the exception type that most closely matches. The closeness of a match is determined by the inheritance chain. For example, even though the exception thrown is of type System.Exception, this “is a” relationship occurs through inheritance because System.InvalidOperationException ultimately derives from System.Exception. Since InvalidOperationException most closely matches the exception thrown, catch(InvalidOperationException ...) will catch the exception instead of the catch(Exception...) block.

Catch blocks must appear in order, from most specific to most general, to avoid a compile error. For example, moving the catch(Exception ...) block before any of the other exceptions will result in a compile error, since all prior exceptions derive from System.Exception at some point in their inheritance chain.

As shown with the catch (SystemException){ }) block, a named parameter for the catch block is not required. In fact, a final catch without even the type parameter is allowable, as you will see in the next section.


Language Contrast: Java—Exception Specifiers

C# has no equivalent for Java’s exception specifiers. With exception specifiers, the Java compiler is able to verify that all possible exceptions thrown within a function (or a function’s call hierarchy) are either caught or declared as possibly rethrown. The C# team considered this option and concluded that the maintenance burden that it imposed was not worth the perceived benefit. Therefore, it is not necessary to maintain a list of all possible exceptions throughout a particular call stack, but neither is it feasible to easily determine the possible exceptions. (As it turns out, this wasn’t possible for Java either. Calling virtual methods or using late binding, such as reflection, made it impossible to fully resolve at compile time what exceptions a method could possibly throw.)


Notice that in the InvalidOperationException catch block there appears a throw statement without identifying the exception to throw (throw is on its own) even though there is the exception instance (exception) in the catch block scope that could be re-thrown. Throwing a specific exception would update all the stack information to match the new throw location. As a result, all the stack information indicating the call site the exception originally occurred would be lost making it significantly more difficult to diagnose the problem. For this reason, C# supports a throw statement without the explicit exception reference as long as it occurs within a catch block. This way, code can examine the exception to determine if it is possible to fully handle it, and if not, re-throw the exception (even though not specified explicitly) as though it was never caught and without replacing any stack information.


Language Contrast: Java—Exception Specifiers

C# has no equivalent for Java’s exception specifiers. With exception specifiers, the Java compiler is able to verify that all possible exceptions thrown within a function (or a function’s call hierarchy) are either caught or declared as possibly rethrown. The C# team considered this option and concluded that the maintenance burden that it imposed was not worth the perceived benefit. Therefore, it is not necessary to maintain a list of all possible exceptions throughout a particular call stack, but neither is it feasible to easily determine the possible exceptions. (As it turns out, this wasn’t possible for Java either. Calling virtual methods or using late binding, such as reflection, made it impossible to fully resolve at compile time what exceptions a method could possibly throw.)


General Catch Block

C# requires that any object that code throws must derive from System.Exception. However, this requirement is not universal to all languages. C/C++, for example, allows any object type to be thrown, including managed exceptions that don’t derive from System.Exception. Starting with C# 2.0, all exceptions, whether deriving from System.Exception or not, will propagate into C# assemblies as derived from System.Exception. The result is that System.Exception catch blocks will catch all exceptions not caught by earlier blocks.

C# also supports a general catch block (catch{ }) that behaves identically to the catch(System.Exception exception) block except that there is no type or variable name. Also, the general catch block must appear last within the list of catch blocks. Since the general catch block is identical to the catch(System.Exception exception) block and the general catch block must appear last, the compiler issues a warning if both exist within the same try/catch statement because the general catch block will never be invoked (see the Advanced Topic, General Catch Blocks in C# 1.0, for more information on general catch blocks).

Guidelines for Exception Handling

Exception handling provides much-needed structure to the error-handling mechanisms that preceded it. However, it can still make for some unwieldy results if used haphazardly. The following guidelines offer some best practices for exception handling.

Catch only the exceptions that you can handle.

Generally it is possible to handle some types of exceptions but not others. For example, opening a file for exclusive read-write access may throw a System.IO.IOException because the file is already in use. In catching this type of exception, the code can report to the user that the file is in use and allow the user the option of canceling the operation or retrying it. Only exceptions for which there is a known action should be caught. Other exception types should be left for callers higher in the stack.

Don’t hide (bury) exceptions you don’t fully handle.

New programmers are often tempted to catch all exceptions and then continue executing instead of reporting an unhandled exception to the user. However, this may result in a critical system problem going undetected. Unless code takes explicit action to handle an exception or explicitly determines certain exceptions to be innocuous, catch blocks should rethrow exceptions instead of catching them and hiding them from the caller. Predominantly, catch(System.Exception) and general catch blocks should occur higher in the call stack, unless the block ends by rethrowing the exception.

Use System.Exception and general catch blocks rarely.

Virtually all exceptions derive from System.Exception. However, the best way to handle some System.Exceptions is to allow them to go unhandled or to gracefully shut down the application sooner rather than later. These exceptions include things such as System.OutOfMemoryException and System.StackOverflowException. In CLR 4, such exceptions were defaulted to nonrecoverable such that catching them without rethrowing them will cause the CLR to rethrow them anyway. These exceptions are runtime exceptions that the developer cannot write code to recover from. Therefore, the best course of action is to shut down the application—something the runtime will force in CLR 4 and later. Code prior to CLR 4 should catch such exceptions only to run cleanup or emergency code (such as saving any volatile data) before shutting down the application or rethrowing the exception with throw;.

Avoid exception reporting or logging lower in the call stack.

Often, programmers are tempted to log exceptions or report exceptions to the user at the soonest possible location in the call stack. However, these locations are seldom able to handle the exception fully and they resort to rethrowing the exception. Such catch blocks should not log the exception or report it to a user while in the bowels of the call stack. If the exception is logged and rethrown, the callers higher in the call stack may do the same, resulting in duplicate log entries of the exception. Worse, displaying the exception to the user may not be appropriate for the type of application. (Using System.Console.WriteLine() in a Windows application will never be seen by the user, for example, and displaying a dialog in an unattended command-line process may go unnoticed and freeze the application.) Logging- and exception-related user interfaces should be reserved for high up in the call stack.

Use throw; rather than throw <exception object> inside a catch block.

It is possible to rethrow an exception inside a catch block. For example, the implementation of catch(ArgumentNullException exception) could include a call to throw exception. However, rethrowing the exception like this will reset the stack trace to the location of the rethrown call, instead of reusing the original throw point location. Therefore, unless you are rethrowing with a different exception type or intentionally hiding the original call stack, use throw; to allow the same exception to propagate up the call stack.

Use caution when rethrowing different exceptions.

From inside a catch block, rethrowing a different exception will not only reset the throw point, it will also hide the original exception. To preserve the original exception set the new exception’s InnerException property, generally assignable via the constructor. Rethrowing a different exception should be reserved for the following situations.

1. Changing the exception type clarifies the problem.

For example, in a call to Logon(User user), rethrowing a different exception type is perhaps more appropriate than propagating System.IO.IOException when the file with the user list is inaccessible.

2. Private data is part of the original exception.

In the preceding scenario, if the file path is included in the original System.IO.IOException, thereby exposing private security information about the system, the exception should be wrapped. This assumes, of course, that InnerException is not set with the original exception. (Funnily enough, a very early version of CLR v1 [pre-alpha even] had an exception that said something like “Security exception: You do not have permission to determine the path of c: empfoo.txt”.)

3. The exception type is too specific for the caller to handle appropriately.

For example, instead of throwing an exception specific to a particular database system, a more generic exception is used so that database-specific code higher in the call stack can be avoided.


Guidelines

AVOID exception reporting or logging lower in the call stack.

DO NOT over-catch. Exceptions should be allowed to propagate up the call stack unless it is clearly understood how to programmatically address the error lower in the stack.

CONSIDER catching a specific exception when you understand why it was thrown in a given context and can respond to the failure programmatically.

AVOID catching System.Exception or System.SystemException except in top-level exception handlers to make perform final cleanup operations before rethrowing the exception.

DO use throw rather than throw <exception object> inside a catch block.

DO use caution when rethrowing different exceptions.

DO NOT throw a NullRefernceException, favoring ArgumentNullException instead when a value is unexpectedly null.


Defining Custom Exceptions

Once throwing an exception becomes the best course of action, it is preferable to use framework exceptions because they are well established and understood. Instead of throwing a custom invalid argument exception, for example, it is preferable to use the System.ArgumentException type. However, if the developers using a particular API will take special action—the exception-handling logic will vary to handle a custom exception type, for instance—it is appropriate to define a custom exception. For example, if a mapping API receives an address for which the ZIP Code is invalid, instead of throwing System.ArgumentException, it may be better to throw a custom InvalidAddressException. The key is whether the caller is likely to write a specific InvalidAddressException catch block with special handling rather than just a generic System.ArgumentException catch block.

Defining a custom exception simply involves deriving from System.Exception or some other exception type. Listing 10.5 provides an example.

Listing 10.5. Creating a Custom Exception


class DatabaseException : System.Exception
{
  public DatabaseException(
      System.Data.SqlClient.SQLException exception)
  {
      InnerException = exception;
      // ...
  }

  public DatabaseException(
      System.Data.OracleClient.OracleException exception)
  {
      InnerException = exception;
      // ...
  }

  public DatabaseException()
  {
      // ...
  }

  public DatabaseException(string message)
  {
      // ...
  }

  public DatabaseException(
      string message, Exception innerException)
  {
      InnerException = innerException;
      // ...
  }
}

This custom exception might be created to wrap proprietary database exceptions. Since Oracle and SQL Server (for example) each throw different exceptions for similar errors, an application could define a custom exception that standardizes the database-specific exceptions into a common exception wrapper that the application can handle in a standard manner. That way, whether the application was using an Oracle or a SQL Server backend database, the same catch block could be used to handle the error higher up the stack.

The only requirement for a custom exception is that it derives from System.Exception or one of its descendants. However, there are several more good practices for custom exceptions.

• All exceptions should use the “Exception” suffix. This way, their purpose is easily established from the name.

• Generally, all exceptions should include constructors that take no parameters, a string parameter, and a parameter set of a string and an inner exception. Furthermore, since exceptions are usually constructed within the same statement in which they are thrown, any additional exception data should also be allowed as part of the constructor. (The obvious exception to creating all these constructors is if certain data is required and a constructor circumvents the requirements.)

• The inheritance chain should be kept relatively shallow (with fewer than approximately five levels).

The inner exception serves an important purpose when rethrowing an exception that is different from the one that was caught. For example, if a System.Data.SqlClient.SqlException is thrown by a database call but is caught within the data access layer to be rethrown as a DatabaseException, the DatabaseException constructor that takes the SqlException (or inner exception) will save the original SqlException in the InnerException property. That way, when requiring additional details about the original exception, developers can retrieve the exception from the InnerException property (for example, exception.InnerException).


Guidelines

DO NOT create a new exception type if the exception would not be handled differently than an existing CLR exception. Throw the existing framework exception instead.

DO create a new exception type to communicate a unique program error that cannot be communicated using an existing CLR exception and can be programmatically handled in a different way than any other existing CLR exception type.

DO provide a parameterless constructor on all custom exception types. Also provide constructors that take a message and an inner exception.

DO end exception class names with the “Exception” suffix.

DO make exceptions runtime-serializable.

CONSIDER providing exception properties for programmatic access to extra information relevant to the exception.

AVOID deep exception hierarchies.


Wrapping an Exception and Rethrowing

On occasion, an exception thrown at a lower level in the stack will no longer make sense when caught at a higher level. For example, consider a System.IO.IOException that occurs because a system is out of disk space on the server. A client catching such an exception would not necessarily be able to understand the context of why there was even I/O activity. Similarly, consider a geographic coordinate request API that throws a System.UnauthorizedAccessException (an exception totally unrelated to the API called). In this second example, the caller has no context or understanding of what the API call has to do with security. From the perspective of the code that invokes the API, these exceptions cause more confusion than they help diagnose. Instead of exposing such exceptions to the client, it might make sense to catch the exception, and throw a different exception such as InvalidOperationException (or even perhaps a custom exception) as a means of communicating that the system is in an invalid state. In such scenarios, be sure to set the InnerException property of the wrapping exception (generally via the constructor call such as new InvalidOperationException(String, Exception)) so that there is additional context that can be used for diagnostics by someone closer to the framework that was invoked.

An important detail to remember when considering to wrap and rethrow an exception is the fact that the original stack trace—which provides the context of where the exception was thrown—will be replaced with the new stack trace of where the wrapping exception is thrown. Fortunately, by embedding the original exception into the wrapping exception, the original stack trace is still available.

Ultimately, remember that the intended recipient of the exception is the programmer writing code that calls your API—possibly incorrectly. Therefore, provide as much information to her that indicates both what she did wrong and (perhaps more importantly) how to fix it. The exception type is a critical piece of the communication mechanism. Therefore, choose the type carefully.


Guidelines

CONSIDER wrapping specific exceptions thrown from the lower layer in a more appropriate exception if the lower-layer exception does not make sense in the context of the higher-layer operation.

DO specify the inner exception when wrapping exceptions.

DO target developers as the audience for exceptions, identifying both the problem as well as the mechanism to resolve it, where possible.

DO use an empty throw statement (throw;) when rethrowing the same exception rather than passing the exception as an argument to throw.


Summary

Throwing an exception causes a significant performance hit. A single exception causes lots of runtime stack information to be loaded and processed, data that would not otherwise be loaded, and it takes a considerable amount of time. As pointed out in Chapter 4, use exceptions only to handle exceptional circumstances; APIs should provide mechanisms to check whether an exception will be thrown instead of forcing a particular API to be called in order to determine whether an exception will be thrown.

The next chapter introduces generics, a C# 2.0 feature that significantly enhances the code written in C# 1.0. In fact, it essentially deprecates any use of the System.Collections namespace, which was formerly used in nearly every project.

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

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