Chapter 13. Exception Handling: A Deeper Look

 

It is common sense to take a method and try it. If it fails, admit it frankly and try another. But above all, try something.

 
 --Franklin Delano Roosevelt
 

O! throw away the worser part of it, And live the purer with the other half.

 
 --William Shakespeare
 

If they’re running and they don’t look where they’re going I have to come out from somewhere and catch them.

 
 --J. D. Salinger
<feature> <supertitle>Objectives</supertitle>

In this chapter you’ll learn:

<objective>

What exceptions are and how they’re handled.

</objective>
<objective>

When to use exception handling.

</objective>
<objective>

To use try blocks to delimit code in which exceptions might occur.

</objective>
<objective>

To throw exceptions to indicate a problem.

</objective>
<objective>

To use catch blocks to specify exception handlers.

</objective>
<objective>

To use the finally block to release resources.

</objective>
<objective>

The .NET exception class hierarchy.

</objective>
<objective>

Exception properties.

</objective>
<objective>

To create user-defined exceptions.

</objective>
</feature>
<feature> <supertitle>Outline</supertitle> </feature>

Introduction

In this chapter, we take a deeper look at exception handling. As you know from Section 8.4, an exception indicates that a problem occurred during a program’s execution. The name “exception” comes from the fact that, although the problem can occur, it occurs infrequently. If the “rule” is that a statement normally executes correctly, then the occurrence of a problem represents the “exception to the rule.” As we showed in Section 8.4 and in Chapter 10, exception handling enables you to create applications that can handle exceptions—in many cases allowing a program to continue executing as if no problems were encountered. More severe problems may prevent a program from continuing normal execution, instead requiring the program to notify the user of the problem, then terminate in a controlled manner. The features presented in this chapter enable you to write clear, robust and more fault-tolerant programs (i.e., programs that are able to deal with problems that may arise and continue executing). The style and details of C# exception handling are based in part on the work of Andrew Koenig and Bjarne Stroustrup. “Best practices” for exception handling in Visual C# are specified in the Visual Studio documentation.[1].

After reviewing exception-handling concepts and basic exception-handling techniques, we overview .NET’s exception-handling class hierarchy. Programs typically request and release resources (such as files on disk) during program execution. Often, the supply of these resources is limited, or the resources can be used by only one program at a time. We demonstrate a part of the exception-handling mechanism that enables a program to use a resource, then guarantee that it will be released for use by other programs, even if an exception occurs. We show several properties of class System.Exception (the base class of all exception classes) and discuss how you can create and use your own exception classes.

Example: Divide by Zero without Exception Handling

Let’s revisit what happens when errors arise in a console application that does not use exception handling. Figure 13.1 inputs two integers from the user, then divides the first integer by the second using integer division to obtain an int result. In this example, an exception is thrown (i.e., an exception occurs) when a method detects a problem and is unable to handle it.

Example 13.1. Integer division without exception handling.

 1   // Fig. 13.1: DivideByZeroNoExceptionHandling.cs
 2   // Integer division without exception handling.
 3   using System;
 4
 5   class DivideByZeroNoExceptionHandling
 6   {
 7      static void Main()
 8      {
 9         // get numerator and denominator
10         Console.Write( "Please enter an integer numerator: " );
11         int numerator = Convert.ToInt32( Console.ReadLine() );
12         Console.Write( "Please enter an integer denominator: " );
13         int denominator = Convert.ToInt32( Console.ReadLine() );
14
15         // divide the two integers, then display the result
16         int result = numerator / denominator;
17         Console.WriteLine( "
Result: {0:D} / {1:D} = {2:D}",
18            numerator, denominator, result );
19      } // end Main
20   } // end class DivideByZeroNoExceptionHandling
Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14
Please enter an integer numerator: 100
Please enter an integer denominator: 0

Unhandled Exception: System.DivideByZeroException:
   Attempted to divide by zero.
   at DivideByZeroNoExceptionHandling.Main()
      in C:examplesch13Fig13_01DivideByZeroNoExceptionHandling
      DivideByZeroNoExceptionHandling
      DivideByZeroNoExceptionHandling.cs: line 16
Please enter an integer numerator: 100
Please enter an integer denominator: hello

Unhandled Exception: System.FormatException:
   Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options,
      NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style,
      NumberFormatInfo info)
   at DivideByZeroNoExceptionHandling.Main()
      in C:examplesch13Fig13_01DivideByZeroNoExceptionHandling
      DivideByZeroNoExceptionHandling
      DivideByZeroNoExceptionHandling.cs: line 13

Running the Application

In most of our examples, the application appears to run the same with or without debugging. As we discuss shortly, the example in Fig. 13.1 might cause errors, depending on the user’s input. If you run this application using the Debug > Start Debugging menu option, the program pauses at the line where an exception occurs, displays the Exception Assistant and allows you to analyze the current state of the program and debug it. We discuss the Exception Assistant in Section 13.3.3. We discuss debugging in detail in Appendix G.

In this example, we do not wish to debug the application; we simply want to see what happens when errors arise. For this reason, we execute this application from a Command Prompt window. Select Start > All Programs > Accessories > Command Prompt to open a Command Prompt window, then use the cd command to change to the application’s Debug directory. For example, if this application resides in the directory C:examplesch13Fig13_01DivideByZeroNoExceptionHandling on your system, you will type

cd /d C:examplesch13Fig13_01DivideByZeroNoExceptionHandling
   DivideByZeroNoExceptionHandlinginDebug

in the Command Prompt, then press Enter to change to the application’s Debug directory. To execute the application, type

DivideByZeroNoExceptionHandling.exe

in the Command Prompt, then press Enter. If an error arises during execution, a dialog is displayed indicating that the application encountered a problem and needs to close. In Windows Vista and Windows 7, the system tries to find a solution to the problem, then asks you to choose between looking online for a solution to the problem and closing the program. [Note: On some systems a Just-In-Time Debugging dialog is displayed instead. If this occurs, simply click the No button to dismiss the dialog.] At this point, an error message describing the problem is displayed in the Command Prompt. We formatted the error messages in Fig. 13.1 for readability. [Note: Selecting Debug > Start Without Debugging (or <Ctrl> F5) to run the application from Visual Studio executes the application’s so-called release version. The error messages produced by this version of the application may differ from those shown in Fig. 13.1, because of optimizations that the compiler performs to create an application’s release version.]

Analyzing the Results

The first sample execution shows a successful division. In the second, the user enters 0 as the denominator. Several lines of information are displayed in response to the invalid input. This information—known as a stack trace—includes the name of the exception class (System.DivideByZeroException) in a message indicating the problem that occurred and the path of execution that led to the exception, method by method. This information helps you debug a program. The first line of the error message specifies that a DivideByZeroException occurred. When a program divides an integer by 0, the CLR throws a DivideByZeroException (namespace System). The text after the name of the exception, Attempted to divide by zero,” indicates why this exception occurred. Division by zero is not allowed in integer arithmetic. [Note: Division by zero with floating-point values is allowed and results in the value infinity—represented by either constant Double.PositiveInfinity or constant Double.NegativeInfinity, depending on whether the numerator is positive or negative. These values are displayed as Infinity or -Infinity. If both the numerator and denominator are zero, the result of the calculation is the constant Double.NaN (“not a number”), which is returned when a calculation’s result is undefined.]

Each “at” line in a stack trace indicates a line of code in the particular method that was executing when the exception occurred. The “at” line contains the namespace, class and method in which the exception occurred (DivideByZeroNoExceptionHandling.Main), the location and name of the file containing the code (C:examplesch13Fig13_01DivideByZeroNoExceptionHandlingDivideByZeroNoExceptionHandlingDivideByZeroNoExceptionHandling.cs) and the line number (:line 16) where the exception occurred. In this case, the stack trace indicates that the DivideByZeroException occurred when the program was executing line 16 of method Main. The first “at” line in the stack trace indicates the exception’s throw point—the initial point at which the exception occurred (i.e., line 16 in Main). This information makes it easy for you to see where the exception originated, and what method calls were made to get to that point in the program.

In the third sample execution, the user enters the string “hello” as the denominator. This causes a FormatException, and another stack trace is displayed. Our earlier examples that read numeric values from the user assumed that the user would input an integer value, but a noninteger value could be entered. A FormatException (namespace System) occurs, for example, when Convert method ToInt32 receives a string that does not represent a valid integer. Starting from the last “at” line in the stack trace, we see that the exception was detected in line 13 of method Main. The stack trace also shows the other methods that led to the exception being thrown. To perform its task, Convert.ToInt32 calls method Number.ParseInt32, which in turn calls Number.StringToNumber. The throw point occurs in Number.StringToNumber, as indicated by the first “at” line in the stack trace. Method Convert.ToInt32 is not in the stack trace because the compiler optimized this call out of the code—all it does forward its arguments to Number.ParseInt32.

In the sample executions in Fig. 13.1, the program terminates when exceptions occur and stack traces are displayed. This does not always happen—sometimes a program may continue executing even though an exception has occurred and a stack trace has been printed. In such cases, the application may produce incorrect results. The next section demonstrates how to handle exceptions to enable the program to run to normal completion.

Example: Handling DivideByZeroExceptions and FormatExceptions

Now, let’s consider a simple example of exception handling. The application in Fig. 13.2 uses exception handling to process any DivideByZeroExceptions and FormatExceptions that might arise. The application reads two integers from the user (lines 18–21). Assuming that the user provides integers as input and does not specify 0 as the denominator for the division, line 25 performs the division and lines 28–29 display the result. However, if the user inputs a noninteger value or supplies 0 as the denominator, an exception occurs. This program demonstrates how to catch and handle such exceptions—in this case, displaying an error message and allowing the user to enter another set of values.

Example 13.2. FormatException and DivideByZeroException handlers.

 1   // Fig. 13.2: DivideByZeroExceptionHandling.cs
 2   // FormatException and DivideByZeroException handlers.
 3   using System;
 4
 5   class DivideByZeroExceptionHandling
 6   {
 7      static void Main( string[] args )
 8      {
 9         bool continueLoop = true; // determines whether to keep looping
10
11         do
12         {
13            // retrieve user input and calculate quotient                  
14            try                                                            
15            {                                                              
16               // Convert.ToInt32 generates FormatException                
17               // if argument cannot be converted to an integer            
18               Console.Write( "Enter an integer numerator: " );            
19               int numerator = Convert.ToInt32( Console.ReadLine() );      
20               Console.Write( "Enter an integer denominator: " );          
21               int denominator = Convert.ToInt32( Console.ReadLine() );    
22                                                                           
23               // division generates DivideByZeroException                 
24               // if denominator is 0                                      
25               int result = numerator / denominator;                       
26                                                                           
27               // display result                                           
28               Console.WriteLine( "
Result: {0} / {1} = {2}",             
29                  numerator, denominator, result );                        
30               continueLoop = false;                                      
31            } // end try                                                   
32            catch ( FormatException formatException )                      
33            {                                                              
34               Console.WriteLine( "
" + formatException.Message );        
35               Console.WriteLine(                                          
36                  "You must enter two integers. Please try again.
" );    
37            } // end catch                                                 
38            catch ( DivideByZeroException divideByZeroException )          
39            {                                                              
40               Console.WriteLine( "
" + divideByZeroException.Message ); 
41               Console.WriteLine(                                          
42                  "Zero is an invalid denominator. Please try again.
" );
43            } // end catch                                                
44         } while ( continueLoop ); // end do...while
45      } // end Main
46   } // end class DivideByZeroExceptionHandling
Please enter an integer numerator: 100
Please enter an integer denominator: 7

Result: 100 / 7 = 14
Enter an integer numerator: 100
Enter an integer denominator: 0

Attempted to divide by zero.
Zero is an invalid denominator. Please try again.

Enter an integer numerator: 100
Enter an integer denominator: 7

Result: 100 / 7 = 14

Enter an integer numerator: 100
Enter an integer denominator: hello

Input string was not in a correct format.
You must enter two integers. Please try again.

Enter an integer numerator: 100
Enter an integer denominator: 7

Result: 100 / 7 = 14

Sample Outputs

Before we discuss the details of the program, let’s consider the sample outputs in Fig. 13.2. The first sample output shows a successful calculation in which the user enters the numerator 100 and the denominator 7. The result (14) is an int, because integer division always yields an int result. The second sample output demonstrates the result of an attempt to divide by zero. In integer arithmetic, the CLR tests for division by zero and generates a DivideByZeroException if the denominator is zero. The program detects the exception and displays an error message indicating the attempt to divide by zero. The last sample output depicts the result of inputting a non-int value—in this case, the user enters “hello” as the denominator. The program attempts to convert the input strings to ints using method Convert.ToInt32 (lines 19 and 21). If an argument cannot be converted to an int, the method throws a FormatException. The program catches the exception and displays an error message indicating that the user must enter two ints.

Another Way to Convert Strings to Integers

Another way to validate the input is to use the Int32.TryParse method, which converts a string to an int value if possible. All of the numeric types have TryParse methods. The method requires two arguments—one is the string to parse and the other is the variable in which the converted value is to be stored. The method returns a bool value that’s true only if the string was parsed successfully. If the string could not be converted, the value 0 is assigned to the second argument, which is passed by reference so its value can be modified in the calling method. Method TryParse can be used to validate input in code rather than allowing the code to throw an exception.

Enclosing Code in a try Block

Now we consider the user interactions and flow of control that yield the results shown in the sample output windows. Lines 14–31 define a try block enclosing the code that might throw exceptions, as well as the code that’s skipped when an exception occurs. For example, the program should not display a new result (lines 28–29) unless the calculation in line 25 completes successfully.

The user inputs values that represent the numerator and denominator. The two statements that read the ints (lines 19 and 21) call method Convert.ToInt32 to convert strings to int values. This method throws a FormatException if it cannot convert its string argument to an int. If lines 19 and 21 convert the values properly (i.e., no exceptions occur), then line 25 divides the numerator by the denominator and assigns the result to variable result. If denominator is 0, line 25 causes the CLR to throw a DivideByZeroException. If line 25 does not cause an exception to be thrown, then lines 28–29 display the result of the division.

Catching Exceptions

Exception-handling code appears in a catch block. In general, when an exception occurs in a try block, a corresponding catch block catches the exception and handles it. The try block in this example is followed by two catch blocks—one that handles a FormatException (lines 32–37) and one that handles a DivideByZeroException (lines 38–43). A catch block specifies an exception parameter representing the exception that the catch block can handle. The catch block can use the parameter’s identifier (which you choose) to interact with a caught exception object. If there’s no need to use the exception object in the catch block, the exception parameter’s identifier can be omitted. The type of the catch’s parameter is the type of the exception that the catch block handles. Optionally, you can include a catch block that does not specify an exception type—such a catch block (known as a general catch clause) catches all exception types. At least one catch block and/or a finally block (discussed in Section 13.5) must immediately follow a try block.

In Fig. 13.2, the first catch block catches FormatExceptions (thrown by method Convert.ToInt32), and the second catch block catches DivideByZeroExceptions (thrown by the CLR). If an exception occurs, the program executes only the first matching catch block. Both exception handlers in this example display an error-message dialog. After either catch block terminates, program control continues with the first statement after the last catch block (the end of the method, in this example). We’ll soon take a deeper look at how this flow of control works in exception handling.

Uncaught Exceptions

An uncaught exception (or unhandled exception) is an exception for which there’s no matching catch block. You saw the results of uncaught exceptions in the second and third outputs of Fig. 13.1. Recall that when exceptions occur in that example, the application terminates early (after displaying the exception’s stack trace). The result of an uncaught exception depends on how you execute the program—Fig. 13.1 demonstrated the results of an uncaught exception when an application is executed in a Command Prompt. If you run the application from Visual Studio with debugging, and the runtime environment detects an uncaught exception, the application pauses, and a window called the Exception Assistant appears indicating where the exception occurred, the type of the exception and links to helpful information on handling the exception. Figure 13.3 shows the Exception Assistant that’s displayed if the user attempts to divide by zero in the application of Fig. 13.1.

Exception Assistant.

Figure 13.3. Exception Assistant.

Termination Model of Exception Handling

When a method called in a program or the CLR detects a problem, the method or the CLR throws an exception. Recall that the point in the program at which an exception occurs is called the throw point—this is an important location for debugging purposes (as we demonstrate in Section 13.7). If an exception occurs in a try block (such as a FormatException being thrown as a result of the code in lines 19 and 21 in Fig. 13.2), the try block terminates immediately, and program control transfers to the first of the following catch blocks in which the exception parameter’s type matches the type of the thrown exception. In Fig. 13.2, the first catch block catches FormatExceptions (which occur if input of an invalid type is entered); the second catch block catches DivideByZeroExceptions (which occur if an attempt is made to divide by zero). After the exception is handled, program control does not return to the throw point because the try block has expired (which also causes any of its local variables to go out of scope). Rather, control resumes after the last catch block. This is known as the termination model of exception handling. [Note: Some languages use the resumption model of exception handling, in which, after an exception is handled, control resumes just after the throw point.]

If no exceptions occur in the try block, the program of Fig. 13.2 successfully completes the try block by ignoring the catch blocks in lines 32–37 and 38–43, and passing line 43. Then the program executes the first statement following the try and catch blocks. In this example, the program reaches the end of the do...while loop (line 44), so the method terminates, and the program awaits the next user interaction.

The try block and its corresponding catch and finally blocks together form a try statement. It’s important not to confuse the terms “try block” and “try statement”—the term “try block” refers to the block of code following the keyword try (but before any catch or finally blocks), while the term “try statement” includes all the code from the opening try keyword to the end of the last catch or finally block. This includes the try block, as well as any associated catch blocks and finally block.

When a try block terminates, local variables defined in the block go out of scope. If a try block terminates due to an exception, the CLR searches for the first catch block that can process the type of exception that occurred. The CLR locates the matching catch by comparing the type of the thrown exception to each catch’s parameter type. A match occurs if the types are identical or if the thrown exception’s type is a derived class of the catch’s parameter type. Once an exception is matched to a catch block, the code in that block executes and the other catch blocks in the try statement are ignored.

Flow of Control When Exceptions Occur

In the third sample output of Fig. 13.2, the user inputs hello as the denominator. When line 21 executes, Convert.ToInt32 cannot convert this string to an int, so the method throws a FormatException object to indicate that the method was unable to convert the string to an int. When the exception occurs, the try block expires (terminates). Next, the CLR attempts to locate a matching catch block. A match occurs with the catch block in line 32, so the exception handler displays the exception’s Message property (to retrieve the error message associated with the exception) and the program ignores all other exception handlers following the try block. Program control then continues with line 44.

Common Programming Error 13.1

Common Programming Error 13.1

Specifying a comma-separated list of parameters in a catch block is a syntax error. A catch block can have at most one parameter.

In the second sample output of Fig. 13.2, the user inputs 0 as the denominator. When the division in line 25 executes, a DivideByZeroException occurs. Once again, the try block terminates, and the program attempts to locate a matching catch block. In this case, the first catch block does not match—the exception type in the catch-handler declaration is not the same as the type of the thrown exception, and FormatException is not a base class of DivideByZeroException. Therefore the program continues to search for a matching catch block, which it finds in line 38. Line 40 displays the exception’s Message property. Again, program control then continues with line 44.

.NET Exception Hierarchy

In C#, the exception-handling mechanism allows only objects of class Exception (namespace System) and its derived classes to be thrown and caught. Note, however, that C# programs may interact with software components written in other .NET languages (such as C++) that do not restrict exception types. The general catch clause can be used to catch such exceptions.

This section overviews several of the .NET Framework’s exception classes and focuses exclusively on exceptions that derive from class Exception. In addition, we discuss how to determine whether a particular method throws exceptions.

Class SystemException

Class Exception (namespace System) is the base class of .NET’s exception class hierarchy. An important derived class is SystemException. The CLR generates SystemExceptions. Many of these can be avoided if applications are coded properly. For example, if a program attempts to access an out-of-range array index, the CLR throws an exception of type IndexOutOfRangeException (a derived class of SystemException). Similarly, an exception occurs when a program uses a reference-type variable to call a method when the reference has a value of null. This causes a NullReferenceException (another derived class of SystemException). You saw earlier in this chapter that a DivideByZeroException occurs in integer division when a program attempts to divide by zero.

Other exceptions thrown by the CLR include OutOfMemoryException, StackOverflowException and ExecutionEngineException, which are thrown when something goes wrong that causes the CLR to become unstable. Sometimes such exceptions cannot even be caught. It’s best to simply log such exceptions, then terminate your application.

A benefit of the exception class hierarchy is that a catch block can catch exceptions of a particular type or—because of the is-a relationship of inheritance—can use a base-class type to catch exceptions in a hierarchy of related exception types. For example, Section 13.3.2 discussed the catch block with no parameter, which catches exceptions of all types (including those that are not derived from Exception). A catch block that specifies a parameter of type Exception can catch all exceptions that derive from Exception, because Exception is the base class of all exception classes. The advantage of this approach is that the exception handler can access the caught exception’s information via the parameter in the catch. We’ll say more about accessing exception information in Section 13.7.

Using inheritance with exceptions enables an catch block to catch related exceptions using a concise notation. A set of exception handlers could catch each derived-class exception type individually, but catching the base-class exception type is more concise. However, this technique makes sense only if the handling behavior is the same for a base class and all derived classes. Otherwise, catch each derived-class exception individually.

Common Programming Error 13.2

Common Programming Error 13.2

The compiler issues an error if a catch block that catches a base-class exception is placed before a catch block for any of that class’s derived-class types. In this case, the base-class catch block would catch all base-class and derived-class exceptions, so the derived-class exception handler would never execute.

Determining Which Exceptions a Method Throws

How do we determine that an exception might occur in a program? For methods contained in the .NET Framework classes, read the detailed descriptions of the methods in the online documentation. If a method throws an exception, its description contains a section called Exceptions that specifies the types of exceptions the method throws and briefly describes what causes them. For an example, search for “Convert.ToInt32 method” in the Visual Studio online documentation. The Exceptions section of this method’s web page indicates that method Convert.ToInt32 throws two exception types—FormatException and OverflowException—and describes the reason why each might occur. [Note: You can also find this information in the Object Browser described in Section 10.12.]

Software Engineering Observation 13.1

Software Engineering Observation 13.1

If a method throws exceptions, statements that invoke the method directly or indirectly should be placed in try blocks, and those exceptions should be caught and handled.

It’s more difficult to determine when the CLR throws exceptions. Such information appears in the C# Language Specification (available from bit.ly/CSharp4Spec). This document defines C#’s syntax and specifies cases in which exceptions are thrown.

finally Block

Programs frequently request and release resources dynamically (i.e., at execution time). For example, a program that reads a file from disk first makes a file-open request (as we’ll see in Chapter 17, Files and Streams). If that request succeeds, the program reads the contents of the file. Operating systems typically prevent more than one program from manipulating a file at once. Therefore, when a program finishes processing a file, the program should close the file (i.e., release the resource) so other programs can use it. If the file is not closed, a resource leak occurs. In such a case, the file resource is not available to other programs, possibly because a program using the file has not closed it.

In programming languages such as C and C++, in which the programmer is responsible for dynamic memory management, the most common type of resource leak is a memory leak. A memory leak occurs when a program allocates memory (as C# programmers do via keyword new), but does not deallocate the memory when it’s no longer needed. Normally, this is not an issue in C#, because the CLR performs garbage collection of memory that’s no longer needed by an executing program (Section 10.8). However, other kinds of resource leaks (such as unclosed files) can occur.

Error-Prevention Tip 13.1

Error-Prevention Tip 13.1

The CLR does not completely eliminate memory leaks. The CLR will not garbage collect an object until the program contains no more references to that object, and even then there may be a delay until the memory is required. Thus, memory leaks can occur if you inadvertently keep references to unwanted objects.

Moving Resource-Release Code to a finally Block

Typically, exceptions occur when processing resources that require explicit release. For example, a program that processes a file might receive IOExceptions during the processing. For this reason, file-processing code normally appears in a try block. Regardless of whether a program experiences exceptions while processing a file, the program should close the file when it’s no longer needed. Suppose a program places all resource-request and resource-release code in a try block. If no exceptions occur, the try block executes normally and releases the resources after using them. However, if an exception occurs, the try block may expire before the resource-release code can execute. We could duplicate all the resource-release code in each of the catch blocks, but this would make the code more difficult to modify and maintain. We could also place the resource-release code after the try statement; however, if the try block terminated due to a return statement, code following the try statement would never execute.

To address these problems, C#’s exception-handling mechanism provides the finally block, which is guaranteed to execute regardless of whether the try block executes successfully or an exception occurs. This makes the finally block an ideal location in which to place resource-release code for resources that are acquired and manipulated in the corresponding try block. If the try block executes successfully, the finally block executes immediately after the try block terminates. If an exception occurs in the try block, the finally block executes immediately after a catch block completes. If the exception is not caught by a catch block associated with the try block, or if a catch block associated with the try block throws an exception itself, the finally block executes before the exception is processed by the next enclosing try block, which could be in the calling method. By placing the resource-release code in a finally block, we ensure that even if the program terminates due to an uncaught exception, the resource will be deallocated. Local variables in a try block cannot be accessed in the corresponding finally block. For this reason, variables that must be accessed in both a try block, and its corresponding finally block should be declared before the try block.

Error-Prevention Tip 13.2

Error-Prevention Tip 13.2

A finally block typically contains code to release resources acquired in the corresponding try block, which makes the finally block an effective mechanism for eliminating resource leaks.

Performance Tip 13.1

Performance Tip 13.1

As a rule, resources should be released as soon as they’re no longer needed in a program. This makes them available for reuse promptly.

If one or more catch blocks follow a try block, the finally block is optional. However, if no catch blocks follow a try block, a finally block must appear immediately after the try block. If any catch blocks follow a try block, the finally block (if there is one) appears after the last catch block. Only whitespace and comments can separate the blocks in a try statement.

Demonstrating the finally Block

The application in Fig. 13.4 demonstrates that the finally block always executes, regardless of whether an exception occurs in the corresponding try block. The program consists of method Main (lines 8–47) and four other methods that Main invokes to demonstrate finally. These methods are DoesNotThrowException (lines 50–67), ThrowExceptionWithCatch (lines 70–89), ThrowExceptionWithoutCatch (lines 92–108) and ThrowExceptionCatchRethrow (lines 111–136).

Example 13.4. finally blocks always execute, even when no exception occurs.

 1   // Fig. 13.4: UsingExceptions.cs
 2   // Using finally blocks.
 3   // finally blocks always execute, even when no exception occurs.
 4   using System;
 5
 6   class UsingExceptions
 7   {
 8      static void Main()
 9      {
10         // Case 1: No exceptions occur in called method
11         Console.WriteLine( "Calling DoesNotThrowException" );
12         DoesNotThrowException();
13
14         // Case 2: Exception occurs and is caught in called method
15         Console.WriteLine( "
Calling ThrowExceptionWithCatch" );
16         ThrowExceptionWithCatch();
17
18         // Case 3: Exception occurs, but is not caught in called method
19         // because there is no catch block.
20         Console.WriteLine( "
Calling ThrowExceptionWithoutCatch" );
21
22         // call ThrowExceptionWithoutCatch
23         try
24         {
25            ThrowExceptionWithoutCatch();
26         } // end try
27         catch
28         {
29            Console.WriteLine( "Caught exception from " +
30               "ThrowExceptionWithoutCatch in Main" );
31         } // end catch
32
33         // Case 4: Exception occurs and is caught in called method,
34         // then rethrown to caller.
35         Console.WriteLine( "
Calling ThrowExceptionCatchRethrow" );
36
37         // call ThrowExceptionCatchRethrow
38         try
39         {
40            ThrowExceptionCatchRethrow();
41         } // end try
42         catch
43         {
44            Console.WriteLine( "Caught exception from " +
45               "ThrowExceptionCatchRethrow in Main" );
46         } // end catch
47      } // end method Main
48
49      // no exceptions thrown
50      static void DoesNotThrowException()
51      {
52         // try block does not throw any exceptions
53         try
54         {
55            Console.WriteLine( "In DoesNotThrowException" );
56         } // end try
57         catch
58         {
59            Console.WriteLine( "This catch never executes" );
60         } // end catch
61         finally                                                             
62         {                                                                   
63            Console.WriteLine( "finally executed in DoesNotThrowException" );
64         } // end finally                                                    
65
66         Console.WriteLine( "End of DoesNotThrowException" );
67      } // end method DoesNotThrowException
68
69      // throws exception and catches it locally
70      static void ThrowExceptionWithCatch()
71      {
72         // try block throws exception
73         try
74         {
75            Console.WriteLine( "In ThrowExceptionWithCatch" );
76            throw new Exception( "Exception in ThrowExceptionWithCatch" );
77         } // end try
78         catch ( Exception exceptionParameter )
79         {
80            Console.WriteLine( "Message: " + exceptionParameter.Message );
81         } // end catch
82         finally                                               
83         {                                                     
84            Console.WriteLine(                                 
85               "finally executed in ThrowExceptionWithCatch" );
86         } // end finally                                      
87
88         Console.WriteLine( "End of ThrowExceptionWithCatch" );
89      } // end method ThrowExceptionWithCatch
90
91      // throws exception and does not catch it locally
92      static void ThrowExceptionWithoutCatch()
93      {
94         // throw exception, but do not catch it
95         try
96         {
97            Console.WriteLine( "In ThrowExceptionWithoutCatch" );
98            throw new Exception( "Exception in ThrowExceptionWithoutCatch" );
99         } // end try
100        finally                                       
101        {                                             
102           Console.WriteLine( "finally executed in " +
103              "ThrowExceptionWithoutCatch" );         
104        } // end finally                              
105
106        // unreachable code; logic error
107        Console.WriteLine( "End of ThrowExceptionWithoutCatch" );
108      } // end method ThrowExceptionWithoutCatch
109
110      // throws exception, catches it and rethrows it
111      static void ThrowExceptionCatchRethrow()
112      {
113         // try block throws exception
114         try
115         {
116            Console.WriteLine( "In ThrowExceptionCatchRethrow" );
117            throw new Exception( "Exception in ThrowExceptionCatchRethrow" );
118         } // end try
119         catch ( Exception exceptionParameter )
120         {
121            Console.WriteLine( "Message: " + exceptionParameter.Message );
122
123            // rethrow exception for further processing
124            throw ;                                    
125
126            // unreachable code; logic error
127         } // end catch
128         finally                                       
129         {                                             
130            Console.WriteLine( "finally executed in " +
131               "ThrowExceptionCatchRethrow" );         
132         } // end finally                              
133
134         // any code placed here is never reached
135         Console.WriteLine( "End of ThrowExceptionCatchRethrow" );
136      } // end method ThrowExceptionCatchRethrow
137   } // end class UsingExceptions

Calling DoesNotThrowException
In DoesNotThrowException
finally executed in DoesNotThrowException
End of DoesNotThrowException

Calling ThrowExceptionWithCatch
In ThrowExceptionWithCatch
Message: Exception in ThrowExceptionWithCatch
finally executed in ThrowExceptionWithCatch
End of ThrowExceptionWithCatch

Calling ThrowExceptionWithoutCatch
In ThrowExceptionWithoutCatch
finally executed in ThrowExceptionWithoutCatch
Caught exception from ThrowExceptionWithoutCatch in Main

Calling ThrowExceptionCatchRethrow
In ThrowExceptionCatchRethrow
Message: Exception in ThrowExceptionCatchRethrow
finally executed in ThrowExceptionCatchRethrow
Caught exception from ThrowExceptionCatchRethrow in Main

Line 12 of Main invokes method DoesNotThrowException. This method’s try block outputs a message (line 55). Because the try block does not throw any exceptions, program control ignores the catch block (lines 57–60) and executes the finally block (lines 61–64), which outputs a message. At this point, program control continues with the first statement after the close of the finally block (line 66), which outputs a message indicating that the end of the method has been reached. Then, program control returns to Main.

Throwing Exceptions Using the throw Statement

Line 16 of Main invokes method ThrowExceptionWithCatch (lines 70–89), which begins in its try block (lines 73–77) by outputting a message. Next, the try block creates an Exception object and uses a throw statement to throw it (line 76). Executing the throw statement indicates that a problem has occurred in the code. As you’ve seen in earlier chapters, you can throw exceptions by using the throw statement. Just as with exceptions thrown by the Framework Class Library’s methods and the CLR, this indicates to client applications that an error has occurred. A throw statement specifies an object to be thrown. The operand of a throw statement can be of type Exception or of any type derived from class Exception.

The string passed to the constructor becomes the exception object’s error message. When a throw statement in a try block executes, the try block expires immediately, and program control continues with the first matching catch block (lines 78–81) following the try block. In this example, the type thrown (Exception) matches the type specified in the catch, so line 80 outputs a message indicating the exception that occurred. Then, the finally block (lines 82–86) executes and outputs a message. At this point, program control continues with the first statement after the close of the finally block (line 88), which outputs a message indicating that the end of the method has been reached. Program control then returns to Main. In line 80, we use the exception object’s Message property to retrieve the error message associated with the exception (i.e., the message passed to the Exception constructor). Section 13.7 discusses several properties of class Exception.

Lines 23–31 of Main define a try statement in which Main invokes method ThrowExceptionWithoutCatch (lines 92–108). The try block enables Main to catch any exceptions thrown by ThrowExceptionWithoutCatch. The try block in lines 95–99 of ThrowExceptionWithoutCatch begins by outputting a message. Next, the try block throws an Exception (line 98) and expires immediately.

Normally, program control would continue at the first catch following this try block. However, this try block does not have any catch blocks. Therefore, the exception is not caught in method ThrowExceptionWithoutCatch. Program control proceeds to the finally block (lines 100–104), which outputs a message. At this point, program control returns to Main—any statements appearing after the finally block (e.g., line 107) do not execute. In this example, such statements could cause logic errors, because the exception thrown in line 98 is not caught. In Main, the catch block in lines 27–31 catches the exception and displays a message indicating that the exception was caught in Main.

Rethrowing Exceptions

Lines 38–46 of Main define a try statement in which Main invokes method ThrowExceptionCatchRethrow (lines 111–136). The try statement enables Main to catch any exceptions thrown by ThrowExceptionCatchRethrow. The try statement in lines 114–132 of ThrowExceptionCatchRethrow begins by outputting a message. Next, the try block throws an Exception (line 117). The try block expires immediately, and program control continues at the first catch (lines 119–127) following the try block. In this example, the type thrown (Exception) matches the type specified in the catch, so line 121 outputs a message indicating where the exception occurred. Line 124 uses the throw statement to rethrow the exception. This indicates that the catch block performed partial processing of the exception and now is throwing the exception again (in this case, back to the method Main) for further processing. In general, it’s considered better practice to throw a new exception and pass the original one to the new exception’s constructor. This maintains all of the stack-trace information from the original exception. Rethrowing an exception loses the original exception’s stack-trace information.

You can also rethrow an exception with a version of the throw statement which takes an operand that is the reference to the exception that was caught. It’s important to note, however, that this form of throw statement resets the throw point, so the original throw point’s stack-trace information is lost. Section 13.7 demonstrates using a throw statement with an operand from a catch block. In that section, you’ll see that after an exception is caught, you can create and throw a different type of exception object from the catch block and you can include the original exception as part of the new exception object. Class library designers often do this to customize the exception types thrown from methods in their class libraries or to provide additional debugging information.

The exception handling in method ThrowExceptionCatchRethrow does not complete, because the throw statement in line 124 immediately terminates the catch block—if there were any code between line 124 and the end of the block, it would not execute. When line 124 executes, method ThrowExceptionCatchRethrow terminates and returns control to Main. Once again, the finally block (lines 128–132) executes and outputs a message before control returns to Main. When control returns to Main, the catch block in lines 42–46 catches the exception and displays a message indicating that the exception was caught. Then the program terminates.

Returning After a finally Block

The next statement to execute after a finally block terminates depends on the exception-handling state. If the try block successfully completes, or if a catch block catches and handles an exception, the program continues its execution with the next statement after the finally block. However, if an exception is not caught, or if a catch block rethrows an exception, program control continues in the next enclosing try block. The enclosing try could be in the calling method or in one of its callers. It also is possible to nest a try statement in a try block; in such a case, the outer try statement’s catch blocks would process any exceptions that were not caught in the inner try statement. If a try block executes and has a corresponding finally block, the finally block executes even if the try block terminates due to a return statement. The return occurs after the execution of the finally block.

Common Programming Error 13.3

Common Programming Error 13.3

If an uncaught exception is awaiting processing when the finally block executes, and the finally block throws a new exception that’s not caught in the finally block, the first exception is lost, and the new exception is passed to the next enclosing try block.

Error-Prevention Tip 13.3

Error-Prevention Tip 13.3

When placing code that can throw an exception in a finally block, always enclose the code in a try statement that catches the appropriate exception types. This prevents the loss of any uncaught and rethrown exceptions that occur before the finally block executes.

Software Engineering Observation 13.2

Software Engineering Observation 13.2

Do not place try blocks around every statement that might throw an exception—this can make programs difficult to read. Instead, place one try block around a significant portion of code, and follow this try block with catch blocks that handle each possible exception. Then follow the catch blocks with a single finally block. Use separate try blocks to distinguish between multiple statements that can throw the same exception type.

The using Statement

Typically resource-release code should be placed in a finally block to ensure that a resource is released, regardless of whether there were exceptions when the resource was used in the corresponding try block. An alternative notation—the using statement (not to be confused with the using directive for using namespaces)—simplifies writing code in which you obtain a resource, use the resource in a try block and release the resource in a corresponding finally block. For example, a file-processing application (Chapter 17) could process a file with a using statement to ensure that the file is closed properly when it’s no longer needed. The resource must be an object that implements the IDisposable interface and therefore has a Dispose method. The general form of a using statement is

using ( ExampleObject exampleObject = new ExampleObject() )
{
   exampleObject.SomeMethod();
}

where ExampleObject is a class that implements the IDisposable interface. This code creates an object of type ExampleObject and uses it in a statement, then calls its Dispose method to release any resources used by the object. The using statement implicitly places the code in its body in a try block with a corresponding finally block that calls the object’s Dispose method. For instance, the preceding code is equivalent to

{
   ExampleObject exampleObject = new ExampleObject();
   try
   {
      exampleObject.SomeMethod();
   }
   finally
   {
      if ( exampleObject != null )
         ( ( IDisposable ) exampleObject ).Dispose();
   }
}

The if statement ensures that exampleObject still references an object; otherwise, a NullReferenceException might occur.

Exception Properties

As we discussed in Section 13.4, exception types derive from class Exception, which has several properties. These frequently are used to formulate error messages indicating a caught exception. Two important properties are Message and StackTrace. Property Message stores the error message associated with an Exception object. This message can be a default message associated with the exception type or a customized message passed to an Exception object’s constructor when the Exception object is thrown. Property StackTrace contains a string that represents the method-call stack. Recall that the runtime environment at all times keeps a list of open method calls that have been made but have not yet returned. The StackTrace represents the series of methods that have not finished processing at the time the exception occurs. If the debugging information that is generated by the compiler for the method is accessible to the IDE, the stack trace also includes line numbers; the first line number indicates the throw point, and subsequent line numbers indicate the locations from which the methods in the stack trace were called. PDB files are created by the IDE to maintain the debugging information for your projects.

Property InnerException

Another property used frequently by class-library programmers is InnerException. Typically, class library programmers “wrap” exception objects caught in their code so that they then can throw new exception types that are specific to their libraries. For example, a programmer implementing an accounting system might have some account-number processing code in which account numbers are input as strings but represented as ints in the code. Recall that a program can convert strings to int values with Convert.ToInt32, which throws a FormatException when it encounters an invalid number format. When an invalid account-number format occurs, the accounting-system programmer might wish to employ a different error message than the default message supplied by FormatException or might wish to indicate a new exception type, such as InvalidAccountNumberFormatException. In such cases, you would provide code to catch the FormatException, then create an appropriate type of Exception object in the catch block and pass the original exception as one of the constructor arguments. The original exception object becomes the InnerException of the new exception object. When an InvalidAccountNumberFormatException occurs in code that uses the accounting system library, the catch block that catches the exception can obtain a reference to the original exception via property InnerException. Thus the exception indicates both that the user specified an invalid account number and that the problem was an invalid number format. If the InnerException property is null, this indicates that the exception was not caused by another exception.

Other Exception Properties

Class Exception provides other properties, including HelpLink, Source and TargetSite. Property HelpLink specifies the location of the help file that describes the problem that occurred. This property is null if no such file exists. Property Source specifies the name of the application or object that caused the exception. Property TargetSite specifies the method where the exception originated.

Demonstrating Exception Properties and Stack Unwinding

Our next example (Fig. 13.5) demonstrates properties Message, StackTrace and InnerException of class Exception. In addition, the example introduces stack unwinding—when an exception is thrown but not caught in a particular scope, the method-call stack is “unwound,” and an attempt is made to catch the exception in the next outer try block. We keep track of the methods on the call stack as we discuss property StackTrace and the stack-unwinding mechanism. To see the proper stack trace, you should execute this program using steps similar to those presented in Section 13.2.

Example 13.5. Stack unwinding and Exception class properties.

 1   // Fig. 13.5: Properties.cs
 2   // Stack unwinding and Exception class properties.
 3   // Demonstrates using properties Message, StackTrace and InnerException.
 4   using System;
 5
 6   class Properties
 7   {
 8      static void Main()
 9      {
10         // call Method1; any Exception generated is caught
11         // in the catch block that follows
12         try
13         {
14            Method1();
15         } // end try
16         catch ( Exception exceptionParameter )
17         {
18            // output the string representation of the Exception, then output
19            // properties Message, StackTrace and InnerException
20            Console.WriteLine( "exceptionParameter.ToString: 
{0}
",
21               exceptionParameter );
22            Console.WriteLine( "exceptionParameter.Message: 
{0}
",
23               exceptionParameter.Message );
24            Console.WriteLine( "exceptionParameter.StackTrace: 
{0}
",
25               exceptionParameter.StackTrace );
26            Console.WriteLine( "exceptionParameter.InnerException: 
{0}
",
27               exceptionParameter.InnerException );
28         } // end catch
29      } // end method Main
30
31      // calls Method2
32      static void Method1()
33      {
34         Method2();
35      } // end method Method1
36
37      // calls Method3
38      static void Method2()
39      {
40         Method3();
41      } // end method Method2
42
43      // throws an Exception containing an InnerException
44      static void Method3()
45      {
46         // attempt to convert string to int
47         try
48         {
49            Convert.ToInt32( "Not an integer" );
50         } // end try
51         catch ( FormatException formatExceptionParameter )
52         {
53            // wrap FormatException in new Exception
54            throw new Exception( "Exception occurred in Method3" ,
55               formatExceptionParameter );                        
56         } // end catch
57      } // end method Method3
58   } // end class Properties
exceptionParameter.ToString:
System.Exception: Exception occurred in Method3 --->
   System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options,
      NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style,
      NumberFormatInfo info)
   at Properties.Method3() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs:line 49
   --- End of inner exception stack trace ---
   at Properties.Method3() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs:line 54
   at Properties.Method2() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs:line 40
   at Properties.Method1() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs:line 34
   at Properties.Main() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs:line 14

exceptionParameter.Message:
Exception occurred in Method3

exceptionParameter.StackTrace:
   at Properties.Method3() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs: line 54
   at Properties.Method2() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs: line 40
   at Properties.Method1() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs: line 34
   at Properties.Main() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs: line 14
exceptionParameter.InnerException:
System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options,
      NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseInt32(String s, NumberStyles style,
      NumberFormatInfo info)
   at Properties.Method3() in C:examplesch13Fig13_05Properties
      PropertiesProperties.cs: line 49

Program execution begins with Main, which becomes the first method on the method-call stack. Line 14 of the try block in Main invokes Method1 (declared in lines 32–35), which becomes the second method on the stack. If Method1 throws an exception, the catch block in lines 16–28 handles the exception and outputs information about the exception that occurred. Line 34 of Method1 invokes Method2 (lines 38–41), which becomes the third method on the stack. Then line 40 of Method2 invokes Method3 (lines 44–57), which becomes the fourth method on the stack.

At this point, the method-call stack (from top to bottom) for the program is:

Method3
Method2
Method1
Main

The method called most recently (Method3) appears at the top of the stack; the first method called (Main) appears at the bottom. The try statement (lines 47–56) in Method3 invokes method Convert.ToInt32 (line 49), which attempts to convert a string to an int. At this point, Convert.ToInt32 becomes the fifth and final method on the call stack.

Throwing an Exception with an InnerException

Because the argument to Convert.ToInt32 is not in int format, line 49 throws a FormatException that’s caught in line 51 of Method3. The exception terminates the call to Convert.ToInt32, so the method is removed (or unwound) from the method-call stack. The catch block in Method3 then creates and throws an Exception object. The first argument to the Exception constructor is the custom error message for our example, “Exception occurred in Method3.” The second argument is the InnerException—the FormatException that was caught. The StackTrace for this new exception object reflects the point at which the exception was thrown (lines 54–55). Now Method3 terminates, because the exception thrown in the catch block is not caught in the method body. Thus, control returns to the statement that invoked Method3 in the prior method in the call stack (Method2). This removes, or unwinds, Method3 from the method-call stack.

When control returns to line 40 in Method2, the CLR determines that line 40 is not in a try block. Therefore the exception cannot be caught in Method2, and Method2 terminates. This unwinds Method2 from the call stack and returns control to line 34 in Method1.

Here again, line 34 is not in a try block, so Method1 cannot catch the exception. The method terminates and is unwound from the call stack, returning control to line 14 in Main, which is located in a try block. The try block in Main expires and the catch block (lines 16–28) catches the exception. The catch block uses properties Message, StackTrace and InnerException to create the output. Stack unwinding continues until a catch block catches the exception or the program terminates.

Displaying Information About the Exception

The first block of output (which we reformatted for readability) in Fig. 13.5 contains the exception’s string representation, which is returned from an implicit call to method ToString. The string begins with the name of the exception class followed by the Message property value. The next four items present the stack trace of the InnerException object. The remainder of the block of output shows the StackTrace for the exception thrown in Method3. The StackTrace represents the state of the method-call stack at the throw point of the exception, rather than at the point where the exception eventually is caught. Each StackTrace line that begins with “at” represents a method on the call stack. These lines indicate the method in which the exception occurred, the file in which the method resides and the line number of the throw point in the file. The inner-exception information includes the inner-exception stack trace.

Error-Prevention Tip 13.4

Error-Prevention Tip 13.4

When catching and rethrowing an exception, provide additional debugging information in the rethrown exception. To do so, create an Exception object containing more specific debugging information, then pass the original caught exception to the new exception object’s constructor to initialize the InnerException property.

The next block of output (two lines) simply displays the Message property’s value (Exception occurred in Method3) of the exception thrown in Method3.

The third block of output displays the StackTrace property of the exception thrown in Method3. This StackTrace property contains the stack trace starting from line 54 in Method3, because that’s the point at which the Exception object was created and thrown. The stack trace always begins from the exception’s throw point.

Finally, the last block of output displays the string representation of the InnerException property, which includes the namespace and class name of the exception object, as well as its Message and StackTrace properties.

User-Defined Exception Classes

In many cases, you can use existing exception classes from the .NET Framework Class Library to indicate exceptions that occur in your programs. In some cases, however, you might wish to create new exception classes specific to the problems that occur in your programs. User-defined exception classes should derive directly or indirectly from class Exception of namespace System. When you create code that throws exceptions, they should be well documented, so that other developers who use your code will know how to handle them.

Good Programming Practice 13.1

Good Programming Practice 13.1

Associating each type of malfunction with an appropriately named exception class improves program clarity.

Software Engineering Observation 13.3

Software Engineering Observation 13.3

Before creating a user-defined exception class, investigate the existing exceptions in the .NET Framework Class Library to determine whether an appropriate exception type already exists.

Class NegativeNumberException

Figures 13.613.7 demonstrate a user-defined exception class. NegativeNumberException (Fig. 13.6) represents exceptions that occur when a program performs an illegal operation on a negative number, such as attempting to calculate its square root.

Example 13.6. NegativeNumberException represents exceptions caused by illegal operations performed on negative numbers.

 1   // Fig. 13.6: NegativeNumberException.cs
 2   // NegativeNumberException represents exceptions caused by
 3   // illegal operations performed on negative numbers.
 4   using System;
 5
 6   class NegativeNumberException : Exception
 7   {
 8      // default constructor                                 
 9      public NegativeNumberException()                       
10         : base( "Illegal operation for a negative number" )
11      {                                                      
12         // empty body                                       
13      } // end default constructor                           
14
15      // constructor for customizing error message         
16      public NegativeNumberException( string messageValue )
17         : base( messageValue )                           
18      {                                                    
19         // empty body                                     
20      } // end one-argument constructor                    
21
22      // constructor for customizing the exception's error
23      // message and specifying the InnerException object 
24      public NegativeNumberException( string messageValue,
25         Exception inner )                                
26         : base( messageValue, inner )                   
27      {                                                   
28         // empty body                                    
29      } // end two-argument constructor                   
30   } // end namespace SquareRootTest

Example 13.7. Demonstrating a user-defined exception class.

 1   // Fig. 13.7: SquareRootTest.cs
 2   // Demonstrating a user-defined exception class.
 3   using System;
 4
 5   class SquareRootTest
 6   {
 7      static void Main( string [] args )
 8      {
 9         bool continueLoop = true ;
10
11         do
12         {
13            // catch any NegativeNumberException thrown
14            try
15            {
16               Console.Write(
17                  "Enter a value to calculate the square root of: " );
18               double inputValue = Convert.ToDouble( Console.ReadLine() );
19               double result = SquareRoot( inputValue );
20
21               Console.WriteLine( "The square root of {0} is {1:F6}
",
22                  inputValue, result );
23               continueLoop = false;
24            } // end try
25            catch ( FormatException formatException )
26            {
27               Console.WriteLine( "
" + formatException.Message );
28               Console.WriteLine( "Please enter a double value.
" );
29            } // end catch
30            catch ( NegativeNumberException negativeNumberException )
31            {
32               Console.WriteLine( "
" + negativeNumberException.Message );
33               Console.WriteLine( "Please enter a non-negative value.
" );
34            } // end catch
35         } while ( continueLoop );
36      } // end Main
37
38      // computes square root of parameter; throws
39      // NegativeNumberException if parameter is negative
40      public static double SquareRoot( double value )
41      {
42         // if negative operand, throw NegativeNumberException
43         if ( value < 0 )
44            throw new NegativeNumberException(                  
45               "Square root of negative number not permitted" );
46         else
47            return Math.Sqrt( value ); // compute square root
48      } // end method SquareRoot
49   } // end class SquareRootTest
Enter a value to calculate the square root of: 30
The square root of 30 is 5.477226
Enter a value to calculate the square root of: hello

Input string was not in a correct format.
Please enter a double value.

Enter a value to calculate the square root of: 25
The square root of 25 is 5.000000
Enter a value to calculate the square root of: -2

Square root of negative number not permitted
Please enter a non-negative value.

Enter a value to calculate the square root of: 2
The square root of 2 is 1.414214

According to Microsoft’s docuemtn on “Best Practices for Handling Exceptions” (bit.ly/ExceptionsBestPractices), user-defined exceptions should typically extend class Exception, have a class name that ends with “Exception” and define three constructors: a parameterless constructor; a constructor that receives a string argument (the error message); and a constructor that receives a string argument and an Exception argument (the error message and the inner-exception object). Defining these three constructors makes your exception class more flexible, allowing other programmers to easily use and extend it.

NegativeNumberExceptions most frequently occur during arithmetic operations, so it seems logical to derive class NegativeNumberException from class ArithmeticException. However, class ArithmeticException derives from class SystemException—the category of exceptions thrown by the CLR. Per Microsoft’s best practices for exception handling, user-defined exception classes should inherit from Exception rather than SystemException. In this case, we could have used the built-in ArgumentException class, which is recommended in the best practices for invalid argument values. We create our own exception type here simply for demonstration purposes.

Class NegativeNumberException

Class SquareRootTest (Fig. 13.7) demonstrates our user-defined exception class. The application enables the user to input a numeric value, then invokes method SquareRoot (lines 40–48) to calculate the square root of that value. To perform this calculation, SquareRoot invokes class Math’s Sqrt method, which receives a double value as its argument. Normally, if the argument is negative, method Sqrt returns NaN. In this program, we’d like to prevent the user from calculating the square root of a negative number. If the numeric value that the user enters is negative, method SquareRoot throws a NegativeNumberException (lines 44–45). Otherwise, SquareRoot invokes class Math’s method Sqrt to compute the square root (line 47).

When the user inputs a value, the try statement (lines 14–34) attempts to invoke SquareRoot using the value input by the user. If the user input is not a number, a FormatException occurs, and the catch block in lines 25–29 processes the exception. If the user inputs a negative number, method SquareRoot throws a NegativeNumberException (lines 44–45); the catch block in lines 30–34 catches and handles this type of exception.

Wrap-Up

In this chapter, you learned how to use exception handling to deal with errors in an application. We demonstrated that exception handling enables you to remove error-handling code from the “main line” of the program’s execution. You saw exception handling in the context of a divide-by-zero example. You learned how to use try blocks to enclose code that may throw an exception, and how to use catch blocks to deal with exceptions that may arise. We explained the termination model of exception handling, in which, after an exception is handled, program control does not return to the throw point. We discussed several important classes of the .NET Exception hierarchy, including Exception (from which user-defined exception classes are derived) and SystemException. Next you learned how to use the finally block to release resources whether or not an exception occurs, and how to throw and rethrow exceptions with the throw statement. We showed how the using statement can be used to automate the process of releasing a resource. You then learned how to obtain information about an exception using Exception properties Message, StackTrace and InnerException, and method ToString. You learned how to create your own exception classes. In the next two chapters, we present an in-depth treatment of graphical user interfaces. In these chapters and throughout the rest of the book, we use exception handling to make our examples more robust, while demonstrating new features of the language.

Summary

Section 13.1 Introduction

  • An exception is an indication of a problem that occurs during a program’s execution.

  • Exception handling enables you to create applications that can resolve (or handle) exceptions.

Section 13.2 Example: Divide by Zero without Exception Handling

  • An exception is thrown when a method or the CLR detects a problem and is unable to handle it.

  • A stack trace includes the name of the exception in a descriptive message that indicates the problem that occurred and the complete method-call stack at the time the exception occurred.

  • Division by zero is not allowed in integer arithmetic.

  • Division by zero is allowed with floating-point values. Such a calculation results in the value infinity, which is represented by Double.PositiveInfinity or Double.NegativeInfinity, depending on whether the numerator is positive or negative. If both the numerator and denominator are zero, the result of the calculation is Double.NaN.

  • When division by zero occurs in integer arithmetic, a DivideByZeroException is thrown.

  • A FormatException occurs when Convert method ToInt32 receives a string that does not represent a valid integer.

Section 13.3 Example: Handling DivideByZeroExceptions and FormatExceptions

  • A try block encloses the code that might throw exceptions, as well as the code that should not execute if an exception occurs.

  • A catch block can specify an identifier representing the exception that the catch block can handle. A general catch clause catches all exception types, but cannot access exception information.

  • At least one catch block and/or a finally block must immediately follow the try block.

  • An uncaught exception is an exception that occurs for which there’s no matching catch block.

  • When a method called in a program detects an exception, or when the CLR detects a problem, the method or the CLR throws an exception.

  • The point in the program at which an exception occurs is called the throw point.

  • If an exception occurs in a try block, the try block terminates immediately, and program control transfers to the first of the following catch blocks in which the exception parameter’s type matches the type of the thrown exception.

  • After an exception is handled, program control does not return to the throw point, because the try block has expired. Instead, control resumes after the try statement’s last catch block. This is known as the termination model of exception handling.

  • The try block and its corresponding catch and finally blocks together form a try statement.

  • The CLR locates the matching catch by comparing the thrown exception’s type to each catch’s exception-parameter type. A match occurs if the types are identical or if the thrown exception’s type is a derived class of the exception-parameter type.

  • Once an exception is matched to a catch block, the other catch blocks are ignored.

Section 13.4 .NET Exception Hierarchy

  • The C# exception-handling mechanism allows objects only of class Exception and its derived classes to be thrown and caught.

  • Class Exception of namespace System is the base class of the .NET Framework Class Library exception class hierarchy.

  • The CLR generates SystemExceptions, which can occur at any point during the execution of the program. Many of these exceptions can be avoided if applications are coded properly.

  • A benefit of using the exception class hierarchy is that a catch block can catch exceptions of a particular type or—because of the is-a relationship of inheritance—can use a base-class type to catch exceptions in a hierarchy of related exception types.

  • A catch block that specifies an exception parameter of type Exception can catch all exceptions that derive from Exception, because Exception is the base class of all exception classes.

  • Using inheritance with exceptions enables an exception handler to catch related exceptions.

Section 13.5 finally Block

  • The most common type of resource leak is a memory leak.

  • A memory leak occurs when a program allocates memory but does not deallocate it when it’s no longer needed. Normally, this is not an issue in C#, because the CLR performs garbage collection of memory that’s no longer needed by an executing program.

  • C#’s exception-handling mechanism provides the finally block, which is guaranteed to execute if program control enters the corresponding try block.

  • The finally block executes regardless of whether the corresponding try block executes successfully or an exception occurs. This makes the finally block an ideal location in which to place resource-release code for resources acquired and manipulated in the corresponding try block.

  • If a try block executes successfully, the finally block executes immediately after the try block terminates. If an exception occurs in the try block, the finally block executes immediately after a catch block completes.

  • If the exception is not caught by a catch block associated with the try block, or if a catch block associated with the try block throws an exception, the finally block executes before the exception is processed by the next enclosing try block (if there is one).

  • A throw statement can rethrow an exception, indicating that a catch block performed partial processing of the exception and now is throwing the exception again for further processing.

  • If a try block executes and has a corresponding finally block, the finally block always executes. The return occurs after the execution of the finally block.

Section 13.6 The using Statement

  • The using statement simplifies writing code in which you obtain a resource, use the resource in a try block and release the resource in a corresponding finally block.

Section 13.7 Exception Properties

  • Property Message of class Exception stores the error message associated with an Exception object.

  • Property StackTrace of class Exception contains a string that represents the method-call stack.

  • Another Exception property used frequently by class library programmers is InnerException. Typically, you use this property to “wrap” exception objects caught in your code, so that you then can throw new exception types specific to your libraries.

  • When an exception is thrown but not caught in a particular scope, stack unwinding occurs and an attempt is made to catch the exception in the next outer try block.

Section 13.8 User-Defined Exception Classes

  • User-defined exception classes should derive directly or indirectly from class Exception of namespace System.

  • User-defined exceptions should typically extend Exception, have a class name that ends with “Exception” and define a parameterless constructor, a constructor that receives a string argument (the error message), and a constructor that receives a string argument and an Exception argument (the error message and the inner-exception object).

Self-Review Exercises

13.1

Fill in the blanks in each of the following statements:

  1. A method is said to_______ an exception when it detects that a problem has occurred.

  2. When present, the_______ block associated with a try block always executes.

  3. Exception classes are derived from class_______.

  4. The statement that throws an exception is called the_______ of the exception.

  5. C# uses the_______ model of exception handling as opposed to the_______ model of exception handling.

  6. An uncaught exception in a method causes the method to_______ from the method-call stack.

  7. Method Convert.ToInt32 can throw a(n)_______ exception if its argument is not a valid integer value.

13.1

  1. throw.

  2. finally.

  3. Exception.

  4. throw point.

  5. termination, resumption.

  6. unwind.

  7. FormatException.

13.2

State whether each of the following is true or false. If false, explain why.

  1. Exceptions always are handled in the method that initially detects the exception.

  2. User-defined exception classes should extend class SystemException.

  3. Accessing an out-of-bounds array index causes the CLR to throw an exception.

  4. A finally block is optional after a try block that does not have any corresponding catch blocks.

  5. A finally block is guaranteed to execute.

  6. It’s possible to return to the throw point of an exception using keyword return.

  7. Exceptions can be rethrown.

  8. Property Message of class Exception returns a string indicating the method from which the exception was thrown.

13.2

  1. False. Exceptions can be handled by other methods on the method-call stack.

  2. False. User-defined exception classes should typically extend class Exception.

  3. True.

  4. False. A try block that does not have any catch blocks requires a finally block.

  5. False. The finally block executes only if program control enters the corresponding try block.

  6. False. return causes control to return to the caller.

  7. True.

  8. False. Property Message of class Exception returns a string representing the error message.

Answers to Self-Review Exercises

Exercises

13.3

(Exception Base Classes and Derived Classes) Use inheritance to create an exception base class and various exception-derived classes. Write a program to demonstrate that the catch specifying the base class catches derived-class exceptions.

13.4

(Catching Exceptions) Write a program that demonstrates how various exceptions are caught with

catch ( Exception ex )

13.5

(Order of Exception Handlers) To demonstrate the importance of the order of exception handlers, write two programs, one with correct ordering of catch blocks (i.e., place the base-class exception handler after all derived-class exception handlers) and another with improper ordering (i.e., place the base-class exception handler before the derived-class exception handlers). What happens when you attempt to compile the second program?

13.6

(Constructor Failure) Exceptions can be used to indicate problems that occur when an object is being constructed. Write a program that shows a constructor passing information about constructor failure to an exception handler. The exception thrown also should contain the arguments sent to the constructor.

13.7

(Rethrowing and Exception) Write a program that demonstrates rethrowing an exception.

13.8

(Not Catching Every Exception) Write a program demonstrating that a method with its own try block does not have to catch every possible exception that occurs within the try block—some exceptions can slip through to, and be handled in, other scopes.

13.9

(Exception from a Deeply Nested Method) Write a program that throws an exception from a deeply nested method. The catch block should follow the try block that encloses the call chain. The exception caught should be one you defined yourself. In catching the exception, display the exception’s message and stack trace.

13.10

(FormatExceptions) Create an application that inputs miles driven and gallons used, and calculates miles per gallon. The example should use exception handling to process the FormatExceptions that occur when converting the input strings to doubles. If invalid data is entered, display a message informing the user.



[1] “Best Practices for Handling Exceptions [C#],” .NET Framework Developer’s Guide, Visual Studio .NET Online Help. Available at msdn.microsoft.com/en-us/library/seyhszts.aspx

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

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