Chapter 13 Exception Handling 2
Chapter 13 Exception Handling 2
Handling
Introduction
• As you know from Section 8.5, 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.
• As we showed in Section 8.5 and in Chapter 10, exception handling enables
you to create apps that can handle exceptions—in many cases allowing a
program to continue executing as if no problems had been encountered.
• More severe problems may prevent a program from continuing normal
execution, instead requiring it 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).
Example: Divide by Zero without Exception
Handling
• Let’s revisit what happens when errors arise in a console app 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: Divide by Zero without Exception
Handling
Example: Divide by Zero without Exception
Handling
Dividing By Zero
• In the second sample execution, 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 exception class’s
name (System.DivideByZeroException) in a message indicating the problem
that occurred and the path of execution that led to the exception, method
by method.
• Stack traces help 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 exception name, “Attempted to divide by zero,” is an
error message that indicates why this exception occurred.
• Division by zero is not allowed in integer arithmetic.
Enter a Non-Numeric Denominator
• In the third sample execution, the user enters "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
int.Parse receives a string that does not represent a valid integer
Unhandled Exceptions Terminate the App
• In the sample executions in Fig. 13.1, the program terminates when
an unhandled exception occurs and a stack trace is displayed.
• This does not always happen—sometimes a program may continue
executing even though an exception has occurred and a stack trace
has been displayed.
• In such cases, the app may produce incorrect results.
• The next section demonstrates how to handle exceptions to enable
the program to run to completion.
Example: Handling DivideByZeroExceptions and
FormatExceptions
• Now, let’s consider a simple example of exception handling.
• The app in Fig. 13.2 uses exception handling to process any
DivideByZeroExceptions and FormatExceptions that might arise.
• The app 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.
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.
Sample Outputs
• 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 the string "hello" as the
denominator.
• The program attempts to convert the input strings to ints using
method int.Parse (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 int.TryParse method,
which converts a string to an int value if possible.
• Like int.Parse, each of the numeric simple types has a TryParse
method.
• TryParse 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
converted successfully to an int.
• If not, TryParse assigns the value 0 to the second argument—that
argument is passed by reference so TryParse can modify its value.
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 some code that will be 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
int.Parse to convert strings to int values.
Enclosing Code in a try Block
• 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.
Catching Exceptions
• 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.
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.
Finally Block
• 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.
Moving Resource-Release Code to a finally Block
• Exceptions often occur when an app uses 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 exit 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 or an exception occurred, code following
the try statement would never execute.
Moving Resource-Release Code to a finally Block
• 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:
Moving Resource-Release Code to a finally Block
• If the try block executes successfully, the finally block executes
immediately after the try block terminates—either by reaching the
block’s closing brace or if a return statement executes in the block.
• If an exception occurs in the try block, the finally block executes
immediately after a catch block completes—either by reaching the
block’s closing brace or if a return statement executes in the block.
• If there is no catch block, 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.
Moving Resource-Release Code to a finally Block
• 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.
Moving Resource-Release Code to a finally Block
• 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.
Demonstrating the finally Block
Demonstrating the finally Block
Demonstrating the finally Block
Demonstrating the finally Block