Exceptions

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 5.  Inheritance and Exceptions in VB.NET


An inevitable part of programming is dealing with error conditions of various sorts. This section introduces the exception-handling mechanism of VB.NET, beginning with a discussion of the fundamentals of error processing and various alternatives that are available. We then carefully go through the VB.NET 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 should check 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 nonstandard. 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, undesirable flow control can leave certain code unexecuted, without performing all its cleanup work.

Furthermore, constructors cannot return a status code, and other methods may be better used for returning useful data rather than a status code.

.NET Exception Handling

VB.NET 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 very similar in concept to that for exceptions in C#, C++, and Java.

Exceptions are implemented in .NET by the Common Language Run-time, 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 a 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 that might encounter an exception is shown below:

 Try     ' code that might cause an exception to be thrown Catch e As ExceptionClass1     ' code to handle this type of exception Catch e As ExceptionClass2     ' code to handle this other type of exception ' possibly more catch handlers Finally   ' cleanup code that is executed whether or not   ' an exception is caught or if catch handler itself   ' throws an exception End Try 

Each catch handler has a parameter specifying the data type of the 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, VB.NET adjusts the stack properly, a process known as stack unwinding . In VB.NET 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).

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.)

Example Program

Now let's look at some code that illustrates the principles we have discussed so far. The program also illustrates using the Exception class to pass a string as a message when we throw an exception. 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 .

 graphics/codeexample.gif ' HotelException.vb - Step 1 Imports System Public Structure ReservationListItem    Public ReservationId As Integer    Public CustomerName As String    Public NumberRooms As Integer End Structure Public Class Hotel    Private m_capacity As Integer    Private m_numGuests As Integer    Private m_nextReservation As Integer = 0    Private m_nextReservationId As Integer = 1    Private Const MAXRESERVATIONS As Integer = 3    Private m_reservations() As ReservationListItem    Public Sub New(ByVal capacity As Integer)       m_capacity = capacity       m_reservations = _          New ReservationListItem(MAXRESERVATIONS - 1) {}    End Sub    Public Function MakeReservation(_     ByVal cust As String, _     ByVal rooms As Integer) As Integer       ' Requested number of rooms should be positive       If rooms <= 0 Then  Throw New Exception(_   "Please request positive number of rooms")  End If       ' Check if rooms are available       If (m_numGuests + rooms > Capacity) Then  Throw New Exception("Rooms not available")  End If       ' Reserve the room for requested dates       m_numGuests += rooms       ' Fill in information for reservation       Dim item As ReservationListItem       item.ReservationId = m_nextReservationId       m_nextReservationId += 1       item.CustomerName = cust       item.NumberRooms = rooms       ' Add reservation to list, return reservation id       reservations(m_nextReservation) = item       m_nextReservation += 1       Return item.ReservationId    End Function    ... 

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.vb Imports System Module TestHotel    Public Sub Main()       Dim iw As InputWrapper = New InputWrapper()       Dim hotel As Hotel = New Hotel(10)       ShowHotel(hotel)       Dim cmd As String       Console.WriteLine("Enter command, quit to exit")       cmd = iw.getString("H> ")       While (Not cmd.Equals("quit"))  Try  If (cmd.Equals("new")) Then                Dim capacity As Integer = _                   iw.getInt("capacity: ")                hotel = New Hotel(capacity)                ShowHotel(hotel)             ElseIf (cmd.Equals("book")) Then                Dim customer As String = _                   iw.getString("customer name: ")                Dim rooms As Integer = _                   iw.getInt("number of rooms: ")                Dim id As Integer = _                   hotel.MakeReservation(_                      customer, rooms)                Console.WriteLine(_                   "Reservation has been booked")                Console.WriteLine(_                   "ReservationId = {0}", id)             ElseIf (cmd.Equals("cancel")) Then                Dim id As Integer = _                   iw.getInt("reservation id: ")                hotel.CancelReservation(id)             ElseIf (cmd.Equals("show")) Then                ShowReservations(hotel)             Else                hotelHelp()             End If  Catch e As Exception  Console.WriteLine(_                "Exception: {0}", e.Message)  End Try  cmd = iw.getString("H> ")       End While    End Sub ... 

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 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.

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 discussed earlier in this chapter.

 graphics/codeexample.gif  Public Class RoomException   Inherits Exception  Private m_available As Integer    Public Sub New(_     ByVal message As String, _     ByVal available As Integer)  MyBase.New(message)  m_available = available    End Sub    Public ReadOnly Property Available() As Integer       Get          Return m_available       End Get    End Property End Class 

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 by calling MyBase.New . We must also modify the code of the Hotel class to throw our new type of exception when too many rooms are requested.

 ' HotelException.vb - Step 2 ... Public Class Hotel    ...    Public Function MakeReservation(_     ByVal cust As String, _     ByVal rooms As Integer) As Integer       ' Requested number of rooms should be positive       If rooms <= 0 Then          Throw New Exception(_             "Please request positive number of rooms")       End If       ' Check if rooms are available  Dim available As Integer = m_capacity   -   m_numGuests   If rooms > available Then   Throw New RoomException(_   "Rooms not available", available)  End If       ... 

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:

 ... ElseIf (cmd.Equals("book")) Then    Dim customer As String = iw.getString(_       "customer name: ")    Dim rooms As Integer = iw.getInt("number of rooms: ")  Dim id As Integer   Try   id = hotel.MakeReservation(customer, rooms)   Catch e As RoomException   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)   End Try   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 all 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 VB.NET 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 that will always display all the reservations. Here is the code:

 graphics/codeexample.gif ElseIf (cmd.Equals("cancel")) Then    Dim id As Integer = _       iw.getInt("reservation id: ")  Try  hotel.CancelReservation(id)  Catch  e As Exception          Console.WriteLine(_             "Exception: {0}", e.Message)          id = iw.getInt("reservation id: ")          hotel.CancelReservation(id)  Finally   ShowReservations(hotel)  End Try ... 

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 list and return reservation id Try    m_reservations(m_nextReservation) = item    m_nextReservation += 1 Catch e As IndexOutOfRangeException  Throw New Exception(_   "Reservation table size exceeded", e)  End Try 

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

 Catch e As Exception    Console.WriteLine("Exception: {0}", e.Message)  If Not e.InnerException Is Nothing Then  Console.WriteLine(_          "InnerException: {0}", _  e.InnerException.Message  )    End If End Try 

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 .

  Catch e As FormatException  Console.WriteLine(_       "Please enter your data in correct format")  Catch e As Exception  Console.WriteLine("Exception: {0}", e.Message)    If Not e.InnerException Is Nothing Then       Console.WriteLine(_          "InnerException: {0}", _          e.InnerException.Message)    End If End Try 

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> 

Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

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