Exceptions

for RuBoard

An inevitable part of programming is dealing with error conditions of various sorts. This section introduces the exception-handling mechanism of C#, beginning with a discussion of the fundamentals of error processing and various alternatives that are available. We then carefully go through the C# exception mechanism, which includes a try block, catch handlers, and a finally block. You can raise exceptions by means of a throw statement. The .NET class library provides an Exception class, which you can use to pass information about an exception that occurred. To further specify your exception and to pass additional information, you can derive your own class from Exception . When handling an exception you may want to throw a new exception. In such a case you can use the "inner exception" feature of the Exception class to pass the original exception on with your new exception.

Exception Fundamentals

The traditional way to deal with runtime errors is to have the functions you call return a status code. The status code may have a particular value for a good return and other values to denote various error conditions. The calling function checks this status code, and if an error was encountered , it performs appropriate error handling. This function in return may pass an error code to its calling function, and so on up the call stack.

Although straightforward, this mechanism has a number of drawbacks. The basic disadvantage is lack of robustness. The called function may have impeccable error-checking code and return appropriate error information, but all this information is useless if the calling function does not make use of it. The program may continue operation as if nothing were amiss and some time later crash for some mysterious reason. Also, status codes are non-standard. A 0 may indicate success in one case but failure in another. And the caller and callee have to agree on return codes and their meaning.

Another disadvantage is that every function in the call stack must participate in the process, or the chain of error information will be broken. Also, unusual flow control can leave memory hanging without being deallocated.

Furthermore, in languages such as C# that have constructors and overloaded operators, there isn't a return value for some operations.

.NET Exception Handling

C# provides an exception mechanism that can be used for reporting and handling errors. An error is reported by "throwing" an exception. The error is handled by "catching" the exception. This mechanism is similar in concept to that for exceptions in C++ and Java.

Exceptions are implemented in .NET by the Common Language Runtime, so exceptions can be thrown in one .NET language and caught in another. The exception mechanism involves the following elements:

  • Code that might encounter an exception should be enclosed in a try block.

  • Exceptions are caught in a catch block.

  • An Exception object is passed as a parameter to catch . The data type of the Exception object is System.Exception or a derived type.

  • You may have multiple catch blocks. A match is made based on the data type of the Exception object.

  • An optional finally clause contains code that will be executed whether or not an exception is encountered.

  • In the called method, an exception is raised through a throw statement.

Exception Flow of Control

The general structure of code which might encounter an exception is shown below:

 try  {        // code that might cause an exception to be thrown  }  catch (ExceptionClass1 e)  {        // code to handle this type of exception  }  catch (ExceptionClass2 e)  {        // code to handle this other type of exception  }  // possibly more catch handlers  // optional finally clause (discussed later)  // statements after try ... catch  finally  {     // cleanup code that is executed whether or not     // an exception is caught or if catch handler itself     // throws an exception  } 

Each catch handler has a parameter specifying the data type of exception that it can handle. The exception data type can be System.Exception or a class ultimately derived from it. If an exception is thrown, the first catch handler that matches the exception data type is executed, and then control passes to the statement just after the catch block(s). If no handler is found, the exception is thrown to the next higher "context" (e.g., the function that called the current one). If no exception is thrown inside the try block, all the catch handlers are skipped .

Context and Stack Unwinding

As the flow of control of a program passes into nested blocks, local variables are pushed onto the stack and a new "context" is entered. Likewise a new context is entered on a method call, which also pushes a return address onto the stack.

If an exception is not handled in the current context, it is passed to successively higher contexts until it is finally handled (or else is "uncaught" and is handled by a default system handler).

When the higher context is entered, C# adjusts the stack properly, a process known as stack unwinding. In C# exception handling, stack unwinding involves both setting the program counter and cleaning up variables (popping stack variables and marking heap variables as free, so that the garbage collector can deallocate them).

Example Program

Now let's look at some code that illustrates the principles we have discussed so far. We will use a simplified version of our Hotel class. This hotel accepts reservations for only a single date. There is a property Capacity and there are methods MakeReservation and CancelReservation . A reservation has an id, a customer name , and the number of rooms requested . (In this example we have added a feature. Previously, a customer could reserve only a single room. We are now allowing multiple room requests . This is to simplify exercising our program to bump against the exception condition of exceeding the capacity of the hotel.) There is a property, NumberReservations , and an indexer to allow the calling program to access the reservation list.

There are several possible exceptions:

  • User does not request a positive number of rooms.

  • Room request exceeds the capacity of the hotel.

  • Index out of range when attempting to store reservation in array of reservations.

The first two exceptions are thrown explicitly by our Hotel class, and the index out-of-range exception is thrown by the .NET library.

Our example program is in the directory HotelException\Step1 .

 // HotelException.cs - Step 1  using System;  public struct ReservationListItem  {     public int ReservationId;     public string CustomerName;     public int NumberRooms;  }  public class Hotel  {     private int capacity;     private int numGuests;     private int nextReservation = 0;     private int nextReservationId = 1;     private const int MAXRESERVATIONS = 3;     private ReservationListItem[] reservations;     public Hotel(int capacity)     {        this.capacity = capacity;        reservations =           new ReservationListItem[MAXRESERVATIONS];     }     public int MakeReservation(string cust, int rooms)     {        // Requested number of rooms should be positive        if (rooms <= 0)  throw new Exception(   "Please request a positive number of rooms");  // Check if rooms are available        if (numGuests + rooms > capacity)  throw new Exception("Rooms not available");  // Reserve the room for requested dates        numGuests += rooms;        // Fill in information for reservation        ReservationListItem item;        item.ReservationId = nextReservationId++;        item.CustomerName = cust;        item.NumberRooms = rooms;        // Add reservation to reservation list and return        // reservation id        reservations[nextReservation++] = item;        return item.ReservationId;     }     ... 

The next code fragment is the test program. Notice that we place the entire body of the command-processing loop inside a try block. The catch handler prints an error message that is passed within the exception object. Then, after either normal processing or displaying an error message, a new command is read in. This simple scheme provides reasonable error processing, as a bad command will not be acted upon, and the user will have an opportunity to enter a new command.

 // Test.cs  using System;  public class TestHotel  {     public static void Main()     {        InputWrapper iw = new InputWrapper();        Hotel hotel = new Hotel(10);        ShowHotel(hotel);        string cmd;        Console.WriteLine("Enter command, quit to exit");        cmd = iw.getString("H> ");        while (! cmd.Equals("quit"))        {  try   {  if (cmd.Equals("new"))              {                 int capacity = iw.getInt("capacity: ");                 hotel = new Hotel(capacity);                 ShowHotel(hotel);              }              else if (cmd.Equals("book"))              {                 string customer =                    iw.getString("customer name: ");                 int rooms = iw.getInt("number of rooms: ");                 int id =                    hotel.MakeReservation(customer, rooms);                 Console.WriteLine(                    "Reservation has been booked");                 Console.WriteLine(                    "ReservationId = {0}", id);              }              else if (cmd.Equals("cancel"))              {                 int id = iw.getInt("reservation id: ");                 hotel.CancelReservation(id);              }              else if (cmd.Equals("show"))                 ShowReservations(hotel);              else                 hotelHelp();  }   catch (Exception e)   {   Console.WriteLine("Exception: {0}", e.Message);   }  cmd = iw.getString("H> ");     }  } 

Here is a transcript of a sample run. We try several kinds of errors.

 The hotel has 10 rooms  Enter command, quit to exit  H> book  customer name: bob  number of rooms: xxx  Exception: Input string was not in a correct format.  H> book  customer name: bob  number of rooms: -5  Exception: Please request a positive number of rooms  H> book  customer name: bob  number of rooms: 5  Reservation has been booked  ReservationId = 1  H> book  customer name: mary  number of rooms: 6  Exception: Rooms not available  H> book  customer name: mary  number of rooms: 3  Reservation has been booked  ReservationId = 2  H> book  customer name: david  number of rooms: 1  Reservation has been booked  ReservationId = 3  H> show  1      bob            5  2      mary           3  3      david          1  H> book  customer name: ellen  number of rooms: 1  Exception: Exception of type   System.IndexOutOfRangeException was thrown.  H> 

Notice that we threw two of the exceptions ourselves . A third (entering "xxx" for the number of rooms) was caught by the .NET library inside our InputWrapper class. A fourth (index out of range) was also caught by .NET, inside the Hotel class. Our catch handler deals with all these different exceptions in a simple, uniform manner.

System.Exception

The System.Exception class provides a number of useful methods and properties for obtaining information about an exception.

  • Message returns a text string providing information about the exception. This message is set when the exception object is constructed . If no message is specified, a generic message will be provided indicating the type of the exception. The Message property is read-only. (Hence, if you want to specify your own message, you must construct a new exception object, as done in the example above.)

  • StackTrace returns a text string providing a stack trace at the place where the exception arose.

  • InnerException holds a reference to another exception. When you throw a new exception, it is desirable not to lose the information about the original exception. The original exception can be passed as a parameter when constructing the new exception. The original exception object is then available through the InnerException property of the new exception. (We will provide an example of using inner exceptions later in this chapter.)

User-Defined Exception Classes

You can do basic exception handling using only the base Exception class, as previously illustrated . In order to obtain finer-grained control over exceptions, it is frequently useful to define your own exception class, derived from Exception . You can then have a more specific catch handler that looks specifically for your exception type. You can also define other members in your derived exception class, so that you can pass additional information to the catch handler.

We will illustrate by enhancing the MakeReservation method of our Hotel class. We want to distinguish between the two types of exceptions we throw. The one type is essentially bad input data (a nonpositive value). We will continue to handle this exception in the same manner as before (that is, bad input data gives rise to a format exception, thrown by .NET library code). We will define a new exception class RoomException to cover the case where the hotel does not have enough rooms to fulfill the request. (In this case we want to allow the user an opportunity to submit another reservation request with fewer rooms.) Our example program is HotelException\Step2 . Here is the definition of our new exception class. This class is defined using inheritance, which we will discuss in Chapter 4, where we will explain the "base(message)" syntax.

 public class RoomException : Exception  {     private int available;     public RoomException(string message, int available)        : base(message)     {        this.available = available;     }     public int Available     {        get        {           return available;        }     }  } 

Note that we define a property Available that can be used to retrieve the information about how many rooms are available. The constructor of our exception class takes two parameters. The first is an error message string, and the second is the number of rooms available. We pass the message string to the constructor of the base class. We must also modify the code of the Hotel class to throw our new type of exception when too many rooms are requested.

 // HotelException.cs - Step 2  ...  public class Hotel  {  ...     public int MakeReservation(string cust, int rooms)     {        // Requested number of rooms should be positive        if (rooms <= 0)           throw new Exception(              "Please request a positive number of rooms");        // Check if rooms are available  int available = capacity - numGuests;   if (rooms > available)   throw new RoomException(   "Rooms not available", available);  ... 

Finally we modify the code in our test program that processes the "book" command. We place the call to MakeReservation inside another try block, and we provide a catch handler for a RoomException . In this catch handler we allow the user an opportunity to request fewer rooms. Here is the code:

 ...  else if (cmd.Equals("book"))  {     string customer = iw.getString("customer name: ");     int rooms = iw.getInt("number of rooms: ");  int id;   try   {   id = hotel.MakeReservation(customer, rooms);   }   catch (RoomException e)   {   Console.WriteLine("Exception: {0}", e.Message);   Console.WriteLine(   "{0} rooms are available", e.Available);   // try again   rooms = iw.getInt("number of rooms: ");   id = hotel.MakeReservation(customer, rooms);   }  Console.WriteLine("Reservation has been booked");     Console.WriteLine("ReservationId = {0}", id);     ... 

Here is a transcript of a sample run of our program:

 The hotel has 10 rooms  Enter command, quit to exit  H> book  customer name: bob  number of rooms: 11  Exception: Rooms not available  10 rooms are available  number of rooms: 5  Reservation has been booked  ReservationId = 1 

Structured Exception Handling

One of the principles of structured programming is that a block of code should have a single entry point and a single exit point. The single exit point is convenient , because you can consolidate cleanup code in one place. The goto statement is usually bad, because it facilitates breaking this principle. But there are other ways to violate the principle of a single exit point, such as multiple return statements from a method.

Multiple return statements may not be too bad, because these may be encountered during normal, anticipated flow of control. But exceptions can cause a particular difficulty, since they interrupt the normal flow of control. In a common scenario you can have at least three ways of exiting a method:

  • No exception is encountered, and any catch handlers are skipped.

  • An exception is caught, and control passes to a catch handler and then to the code after the catch handlers.

  • An exception is caught, and the catch handler itself throws another exception. Then code after the catch handler will be bypassed.

The first two cases do not present a problem, as control passes to the code after the catch handlers. But the third case is a source of difficulty.

Finally Block

The structured exception handling mechanism in C# resolves this problem with a finally block. The finally block is optional, but if present must appear immediately after the catch handlers. It is guaranteed , in all three cases described above, that the code in the finally block will always execute before the method is exited.

We illustrate use of finally in the "cancel" command of our Hotel example. See the directory HotelException\Step3 . There are several ways to exit this block of code, and the user might become confused about whether a cancellation was actually made or not. We insert a finally block which will always display all the reservations. Here is the code:

 else if (cmd.Equals("cancel"))  {     int id;     id = iw.getInt("reservation id: ");     try     {        hotel.CancelReservation(id);     }     catch (Exception e)     {        Console.WriteLine("Exception: {0}", e.Message);        id = iw.getInt("reservation id: ");        hotel.CancelReservation(id);     }  finally   {   ShowReservations(hotel);   }  } 

It is instructive to compare the "book" and "cancel" commands. In the "book" command there is code after the catch handler. This code will be executed if the catch handler is skipped (no exception). The code will also be executed if the catch handler exits normally (user enters a small enough number of rooms). But if an exception is thrown inside the catch handler, this code will be skipped. In the case of "cancel," there is a finally block. The code inside the finally block will always be executed, even if the catch handler throws an exception (user enters an invalid id a second time).

Inner Exceptions

In general it is wise to handle exceptions, at least at some level, near their source, because you have the most information available about the context in which the exception occurred. A common pattern is to create a new exception object that captures more detailed information and throw this onto the calling program. So that information is not lost about the original exception, you may pass the original exception as a parameter when constructing the new exception. Then the calling program can gain access to both exceptions through the InnerException property of the exception object.

The program HotelException\Step3 also illustrates using inner exceptions. In the MakeReservation method we explicitly check for an IndexOutOfRangeException . We throw a new exception, which we construct by passing the original exception as a parameter.

 // Add reservation to reservation list and return  // reservation id  try  {     reservations[nextReservation++] = item;  }  catch (IndexOutOfRangeException e)  {  throw new Exception(   "Reservation table size exceeded", e);  } 

In the test program we make use of the InnerException property.

 catch (Exception e)  {     Console.WriteLine("Exception: {0}", e.Message);  if (e.InnerException != null)   {   Console.WriteLine(   "InnerException: {0}", e.InnerException.Message);   }  } 

Multiple Catch Handlers

You may have several catch handlers for the same try block. Each catches a different type of exception. The first catch handler that matches the exception object will be executed.

The program HotelException\Step3 also illustrates using multiple catch handlers. In the test program we have handlers for both FormatException and Exception . Note that you do not have to instantiate an exception object instance in the catch statement if you do not use it. The catch statement can be used without any parameters if you want to catch any exception and do not care about the exception object.

  catch (FormatException)  {     Console.WriteLine(        "Please enter your data in correct format");  }  catch (Exception e)  {     Console.WriteLine("Exception: {0}", e.Message);     if (e.InnerException != null)     {        Console.WriteLine(           "InnerException: {0}", e.InnerException.Message);     }  } 

Here is a sample run of the program. When we use an incorrect format, the first catch handler is invoked. When we use the correct format, but an illegal negative value for the number of rooms, we don't get a match for the first catch handler, but we do get a match for the second, since we are using the base Exception class.

 The hotel has 10 rooms  Enter command, quit to exit  H> book  customer name: bob  number of rooms: xxx  Please enter your data in correct format  H> book  customer name: bob  number of rooms: -1  Exception: Please request a positive number of rooms  H> 
for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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