3.14. The Exception Class Hierarchy

An exception must represent either an Exception class object or an object of a class derived from Exception. The class framework provides many predefined exception classes. Alternatively, we can derive our own exception classes.

Consider the member function find(). It takes two parameters: a string array and a string item. If either or both arguments are null, find() throws the predefined ArgumentNullException:

public static bool find( string [] table, string item )
{
      if ( table == null || item == null )
      {
            Exception e = new ArgumentNullException();
            if ( table == null )
            {
                 e.Source = "Argument One";
                 if ( item == null )
                      e.Source += " and Argument Two";
            }
            else e.Source = "Argument Two";
            throw e;
      }

      // ...
      return false;
}

The Exception class supports a pocketful of useful properties, including the following:

  • TargetSite, which holds the name of the member function from which this exception is thrown. This is a read-only property. In our example, it is set to Boolean find(System.String[], System.String).

  • Source, which by default holds the name of the assembly from which this exception is thrown. We can explicitly assign it a value, however. In our example it is set to Argument One, Argument Two, or both.

  • Message, which is set to a default message by each exception class. In our example the ArgumentNullException class sets it to Value cannot be null. We can override the default message string in the constructor only; the property is read-only.

  • StackTrace, which is a trace of the call stack leading to the throw. The property is read-only. It includes path, file, and line information—for example,

    at EntryPoint.find(String[] table, String item)
    in c:c#programsexceptionsclass1.cs:line 17
    
    at EntryPoint.Main()
    in c:c#programsexceptionsclass1.cs:line 29
    
  • InnerException, which holds a reference to the inner exception, if any. The property is read-only.

The concept of an inner exception may be unfamiliar. Let's imagine that find() is invoked in a member function named queryText(), as follows:

public static void queryText()
{
   try
   {
       if ( find( get_text_array(), get_item() ))
            // ...
   }
   catch( ArgumentNullException ane )
   {  /* what should we do here? */ }
}

Within queryText(), ArgumentNullException indicates that either one or both of the retrieval routines failed. (We can determine that by examining the Source property of the caught exception.)

To the caller of queryText(), however, the null argument problem is of less concern than the failure of the retrieval routines. What we'd like to do within queryText() is throw back a more explicit exception object, but also return the original exception. We do this through the inner exception.

In other words, an inner exception is an exception that has been only partially handled within a catch clause. Rather than rethrowing the same exception, the developer chooses to create a new and usually more informative exception object. The original exception is passed to the InnerException property in the constructor of the exception, allowing the next exception handler access to both—for example,

catch( ArgumentNullException ane )
{
    string msg;

    switch ( ane.Source )
    {
          case "Argument One":
                msg = "get_text_array() failed";
          break;

          case "Argument Two":
                msg = "get_item() failed";
          break;

          default:
                msg = "both get_text_array() " +
                       "and get_item() failed";
          break;
   }

   throw new InvalidProgramException( msg, ane );
}

The Exception class hierarchy is broken down into two primary subtrees. All the exception classes thrown by the runtime environment are derived from SystemException, which in turn is derived from the Exception class. ArgumentException, ArithmeticException, FormatException, and so on, are all derived from SystemException. These classes are then further derived into the more specialized ArgumentNullException or DivideByZeroException classes.

If we wish to introduce exception classes of our own, the recommendation is to derive them from ApplicationException. This class is directly derived from Exception. It is also recommended that our exception classes provide a default constructor that initializes the class object with default properties—for example,

class TextQueryException : ApplicationException
{
   public TextQueryException()
           : this( "A TextQueryException has been thrown", null )
   {}
   public TextQueryException( string message )
        : this( message, null ){}

   public TextQueryException( string msg, Exception innerE )
        : base( msg, innerE ){}

   // application-specific members go here ...
}

We may wish subsequently to provide more specific subclass exceptions, such as the following:

class ProhibitedQueryException : TextQueryException
{ ... }

When we set up a series of catch clauses, we should always order them such that the more derived class occurs before that of its base. For example, we should always try to catch a TextQueryException object before we try to catch either its immediate base class (ApplicationException) or the base of the base class, and so on.

The reason is that the resolution of an exception is based on a first match rather than on a best match. If a base-class catch clause occurs before that of a derived class, the match is made on the base-class instance and the remaining clauses are not considered. The following ordering of catch clauses handles the most-derived class instances first:

try {
   // ...
}
catch( ProhibitedQueryException pqe )
{ ... }

catch( TextQueryException tqe )
{ ... }

catch( ApplicationException ae )
{ ... }

catch( SystemException se )
{ ... }

catch( Exception e )
{ ... }

Function matching employs a best-match rather than a first-match resolution algorithm. This is why function matching is independent of declaration order. The matching of exception objects is not. Under function matching, the matching of two derived-class objects takes precedence over the matching of a derived-class object with an object of its base class. Under exception matching, the two are treated in effect as equal. This is why we must place the base class exception object after that of its derived instances.

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

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