2. When the app executes, another compiler (known as the just-in-time compiler
13.2 Example: Divide by Zero without Exception
Let’s revisit what happens when errors arise in a console app that does not use exception han- dling. Figure 13.1 inputs two integers from the user, then divides the first integer by the sec- ond using integer division to obtain anintresult. In this example, an exception isthrown (i.e., an exception occurs) when a method detects a problem and is unable to handle it.
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
10 Console.Write( "Please enter an integer numerator: " );
11 12
13 // get denominator
14 Console.Write( "Please enter an integer denominator: " );
15 16
17 // divide the two integers, then display the result 18
19 Console.WriteLine( "\nResult: {0:D} / {1:D} = {2:D}", 20 numerator, denominator, result );
21 } // end Main
22 } // 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
at DivideByZeroNoExceptionHandling.Main()
in C:\examples\ch13\Fig13_01\DivideByZeroNoExceptionHandling\
DivideByZeroNoExceptionHandling\
DivideByZeroNoExceptionHandling.cs:
Fig. 13.1 | Integer division without exception handling. (Part 1 of 2.)
int numerator = Convert.ToInt32( Console.ReadLine() );
int denominator = Convert.ToInt32( Console.ReadLine() );
int result = numerator / denominator;
Unhandled Exception: System.DivideByZeroException:
Attempted to divide by zero.
line 18
13.2 Example: Divide by Zero without Exception Handling 489
Running the App
In most of our examples, an app appears to run the same with or without debugging. As we discuss shortly, the example in Fig. 13.1 might cause exceptions, depending on the us- er’s input. If you run this app in Visual Studio Express 2012 for Windows Desktop using theDEBUG > Start Debuggingmenu option and an exception occurs, the IDE displays a dialog like the one below:
You can click theBreakbutton to pause the program at the line where the exception occurred, allowing you to analyze the program’s state and debug the program. For this example, we do not wish to debug the app; we simply want to see what happens when errors arise. For this reason, we executed this app withDEBUG > Start Without Debugging
If an exception occurs during execution, a dialog appears indicating that the app “has stopped working.” You can simply clickClose the Programto terminate the app. An error message describing the exception that occurred is displayed in the output window. We for- matted the error messages in Fig. 13.1 for readability.
Successful Execution
The first sample execution shows a successful division.
Please enter an integer numerator: 100 Please enter an integer denominator: hello
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:\examples\ch13\Fig13_01\DivideByZeroNoExceptionHandling\
DivideByZeroNoExceptionHandling\
DivideByZeroNoExceptionHandling.cs:
Fig. 13.1 | Integer division without exception handling. (Part 2 of 2.)
Unhandled Exception: System.FormatException:
Input string was not in a correct format.
line 15
Attempting to Divide By Zero
In the second, the user enters0as the denominator. Several lines of information are dis- played in response to the invalid input. This information—known as astack trace—in- cludes 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 mes- sage specifies that aDivideByZeroExceptionoccurred. When a program divides an inte- ger by 0, the CLR throws aDivideByZeroException(namespaceSystem). The text after the exception name, “Attempted to divide by zero,” indicates why this exception oc- curred. Division by zero is not allowed in integer arithmetic.2
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:\examples\ch13\Fig13_01\Divide- ByZeroNoExceptionHandling\DivideByZeroNoExceptionHandling\DivideByZeroNoEx- ceptionHandling.cs) and the line number (:line 18) where the exception occurred. In this case, the stack trace indicates that theDivideByZeroExceptionoccurred when the pro- gram was executing line 18 of methodMain. The first “at” line in the stack trace indicates the exception’sthrow point—the initial point at which the exception occurred (i.e., line 18 inMain). This information makes it easy for you to see which method call caused the excep- tion, and what method calls were made to get to that point in the program.
Attempting to Enter a Non-Integer Value for the Denominator
In the third sample execution, the user enters the string "hello" as the denominator. This causes aFormatException, 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. AFormatException(namespaceSystem) occurs, for example, whenConvertmethodToInt32receives 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 15 of methodMain. The stack trace also shows the other methods that led to the exception being thrown. To perform its task,Convert.ToInt32calls methodNum- ber.ParseInt32, which in turn callsNumber.StringToNumber. The throw point occurs in
Number.StringToNumber, as indicated by the first “at” line in the stack trace. Method
Convert.ToInt32isnot in the stack tracebecause the compileroptimizedthis call out of the code—all it does forward its arguments toNumber.ParseInt32.
Program Termination Due to an Unhandled Exception
In the sample executions in Fig. 13.1, the program terminates when anunhandledexception 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 demon- strates how to handle exceptions to enable the program to run to normal completion.
2. Division by zero with floating-point valuesisallowed and results in the value infinity—represented by either constantDouble.PositiveInfinityor constantDouble.NegativeInfinity, depending on whether the numerator is positive or negative. These values are displayed asInfinityor-Infin- ity. Ifboththe numerator and denominator arezero, the result of the calculation is the constant
Double.NaN(“not a number”), which is returned when a calculation’s result isundefined.
13.3 HandlingDivideByZeroExceptions andFormatExceptions 491
13.3 Example: Handling DivideByZeroException s and FormatException s
Now, let’s consider a simple example of exception handling. The app in Fig. 13.2 uses ex- ception 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 specify0as the denominator for the division, line 25 performs the division and lines 28–29 display the result. However, if the user in- puts a noninteger value or supplies0as the denominator, an exception occurs. This pro- gram demonstrates how tocatchandhandlesuch exceptions—in this case, displaying an error message and allowing the user to enter another set of values.
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 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
Fig. 13.2 | FormatExceptionandDivideByZeroExceptionhandlers. (Part 1 of 2.)
// retrieve user input and calculate quotient try
{
// Convert.ToInt32 generates FormatException // if argument cannot be converted to an integer Console.Write( "Enter an integer numerator: " );
int numerator = Convert.ToInt32( Console.ReadLine() );
Console.Write( "Enter an integer denominator: " );
int denominator = Convert.ToInt32( Console.ReadLine() );
// division generates DivideByZeroException // if denominator is 0
int result = numerator / denominator;
// display result
Console.WriteLine( "\nResult: {0} / {1} = {2}", numerator, denominator, result );
continueLoop = false;
} // end try
catch ( FormatException formatException ) {
Console.WriteLine( "\n" + formatException.Message );
Console.WriteLine(
"You must enter two integers. Please try again.\n" );
} // end catch
catch ( DivideByZeroException divideByZeroException ) {
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 numer- ator100and the denominator7. The result (14) is anint, because integer division always yields anintresult. 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
DivideByZeroExceptionif 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-intvalue—in this case, the user enters"hel-
lo"as the denominator. The program attempts to convert the inputstrings toints using methodConvert.ToInt32(lines 19 and 21). If an argument cannot be converted to an
int, the method throws aFormatException. The program catches the exception and dis- plays an error message indicating that the user must enter twoints.
40 41 42 43
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
Fig. 13.2 | FormatExceptionandDivideByZeroExceptionhandlers. (Part 2 of 2.)
Console.WriteLine( "\n" + divideByZeroException.Message );
Console.WriteLine(
"Zero is an invalid denominator. Please try again.\n" );
} // end catch
13.3 HandlingDivideByZeroExceptions andFormatExceptions 493
Another Way to Convert Strings to Integers
Another way to validate the input is to use theInt32.TryParsemethod, which converts astringto anintvalue if possible. All of the numeric types haveTryParsemethods. The method requires two arguments—one is thestringto parse and the other is the variable in which the converted value is to be stored. The method returns aboolvalue that’strue only if thestringwas parsed successfully. If thestringcould not be converted, the value
0is assigned to the second argument, which is passed by reference so its value can be mod- ified in the calling method. MethodTryParsecan be used to validate input in code rather than allowing the code to throw an exception—this technique is generally preferred.
13.3.1 Enclosing Code in atryBlock
Now we consider the user interactions and flow of control that yield the results shown in the sample output windows. Lines 14–31 define atryblockenclosing the code that might throw exceptions, as well as the code that’s skipped when an exception occurs. For exam- ple, 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 state- ments that read the ints (lines 19 and 21) call method Convert.ToInt32 to convert
strings tointvalues. This method throws aFormatExceptionif it cannot convert its
stringargument to anint. If lines 19 and 21 convert the values properly (i.e., no excep- tions occur), then line 25 divides thenumeratorby thedenominatorand assigns the result to variableresult. Ifdenominatoris0, line 25 causes the CLR to throw aDivideByZero- Exception. If line 25 does not cause an exception to be thrown, then lines 28–29 display the result of the division.
13.3.2 Catching Exceptions
Exception-handling code appears in acatchblock. In general, when an exception occurs in atryblock, a correspondingcatchblockcatchesthe exception andhandlesit. Thetry
block in this example is followed by two catch blocks—one that handles a Format- Exception(lines 32–37) and one that handles aDivideByZeroException(lines 38–43).
Acatchblock specifies an exception parameter representing the exception that thecatch block can handle. Thecatchblock 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 thecatchblock handles. Optionally, you can include acatchblock that doesnotspecify an exception type—such acatchblock (known as ageneralcatchclause) catches all exception types. At least onecatchblock and/or afinallyblock(discussed in Section 13.5) must immediately follow atryblock.
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
catchblock. Both exception handlers in this example display an error-message dialog.
After eithercatchblock terminates, program control continues with the first statement after the last catchblock (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.
13.3.3 Uncaught Exceptions
Anuncaught exception(orunhandled exception) is an exception for which there’s no matchingcatchblock. 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 app termi- nates early (after displaying the exception’s stack trace). The result of an uncaught excep- tion depends on how you execute the program—Fig. 13.1 demonstrated the results of an uncaught exception when an app is executed usingDEBUG > Start Without Debugging. If you run the app from a non-Express version of Visual Studio by usingDEBUG > Start De- buggingand the runtime environment detects an uncaught exception, the app pauses, and theException Assistantwindow appears containing:
• a line pointing from the Exception Assistant to the line of code that caused the exception
• the type of the exception
• Troubleshooting tipswith links to helpful information on what might have caused the exception and how to handle it
• links to view or copy the complete exception details
Figure 13.3 shows the Exception Assistant that’s displayed if the user attempts to divide by zero in the app of Fig. 13.1.
Fig. 13.3 | Exception Assistant.
Throw point Exception Assistant
13.3 HandlingDivideByZeroExceptions andFormatExceptions 495
13.3.4 Termination Model of Exception Handling
Recall that the point in the program at which an exception occurs is called thethrow point—
this is an important location for debugging purposes (as we demonstrate in Section 13.7). If an exception occurs in atryblock (such as aFormatExceptionbeing thrown as a result of the code in lines 19 and 21 in Fig. 13.2), thetryblock terminates immediately, and program control transfers to the first of the followingcatchblocks in which the exception parameter’s type matches that of the thrown exception. In Fig. 13.2, the firstcatchblock catchesFor-
matExceptions (which occur if input of an invalid type is entered); the secondcatchblock catchesDivideByZeroExceptions (which occur if an attempt is made to divide by zero). Af- ter the exception is handled, program control doesnotreturn to the throw point because the
tryblock hasexited(which also causes any of its local variables to go out of scope). Rather, control resumes after the lastcatchblock. This is known as thetermination model of ex- ception handling. [Note:Some languages use theresumption model of exception handling, in which, after an exception is handled, control resumes just after the throw point.]
If no exceptions occur in thetryblock, the program of Fig. 13.2 successfully com- pletes thetryblock by ignoring thecatchblocks in lines 32–37 and 38–43, and passing line 43. Then the program executes the first statement following thetryandcatchblocks.
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.
Thetryblock and its correspondingcatchandfinallyblocks together form atry statement. It’s important not to confuse the terms “tryblock” and “trystatement”—the term “tryblock” refers to the block of code following the keywordtry(but before any
catchorfinallyblocks), while the term “trystatement” includes all the code from the openingtrykeyword to the end of the lastcatchorfinallyblock. This includes thetry block, as well as any associatedcatchblocks andfinallyblock.
When atryblock terminates, local variables defined in the block go out of scope. If atryblock terminates due to an exception, the CLR searches for the firstcatchblock that can process the type of exception that occurred. The CLR locates the matchingcatchby comparing the type of the thrown exception to eachcatch’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 acatchblock, the code in that block executes and the othercatchblocks in thetrystatement are ignored.
13.3.5 Flow of Control When Exceptions Occur
In the third sample output of Fig. 13.2, the user inputshelloas the denominator. When line 21 executes,Convert.ToInt32cannot convert thisstringto anint, so the method throws aFormatExceptionobject to indicate that the method was unable to convert the
stringto anint. When the exception occurs, thetryblock exits (terminates). Next, the CLR attempts to locate a matchingcatchblock. A match occurs with thecatchblock in line 32, so the exception handler displays the exception’sMessageproperty (to retrieve the error message associated with the exception) and the program ignores all other exception handlers following thetryblock. Program control then continues with line 44.
Common Programming Error 13.1
Specifying a comma-separated list of parameters in acatchblock is a syntax error. A
catchblock can have at most one parameter.
In the second sample output of Fig. 13.2, the user inputs0as the denominator. When the division in line 25 executes, aDivideByZeroExceptionoccurs. Once again, thetry block terminates, and the program attempts to locate a matchingcatchblock. In this case, the firstcatchblock does not match—the exception type in thecatch-handler declaration is not the same as the type of the thrown exception, andFormatExceptionis not a base class of DivideByZeroException. Therefore the program continues to search for a matchingcatchblock, which it finds in line 38. Line 40 displays the exception’sMessage property. Again, program control then continues with line 44.
13.4 .NET Exception Hierarchy
In C#, the exception-handling mechanism allows only objects of class Exception (namespaceSystem) 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 generalcatchclause 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 classException. In addition, we discuss how to determine whether a particular method throws exceptions.
13.4.1 ClassSystemException
ClassException(namespaceSystem) is the base class of .NET’s exception class hierarchy.
An important derived class isSystemException. The CLR generatesSystemExceptions.
Many of these can be avoided if apps are coded properly. For example, if a program at- tempts to access anout-of-range array index, the CLR throws an exception of typeIn-
dexOutOfRangeException(a derived class ofSystemException). Similarly, an exception occurs when a program uses a reference-type variable to call a method when the reference has a value ofnull. This causes aNullReferenceException(another derived class ofSys-
temException). You saw earlier in this chapter that aDivideByZeroExceptionoccurs in integer division when a program attempts to divide by zero.
Other exceptions thrown by the CLR includeOutOfMemoryException,StackOver-
flowExceptionandExecutionEngineException, 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 (using a tool such as Apache’slog4net—
logging.apache.org/log4net/), then terminate your app.
A benefit of the exception class hierarchy is that acatchblock can catch exceptions of a particular type or—because of theis-arelationship 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 thecatchblock with no parameter, which catches exceptions of all types (including those that are not derived fromException). Acatchblock that spec- ifies a parameter of typeExceptioncan catch all exceptions that derive fromException, becauseExceptionis 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 param- eter in thecatch. We’ll say more about accessing exception information in Section 13.7.
Using inheritance with exceptions enables acatchblock to catch related exceptions using a concise notation. A set of exception handlers could catch each derived-class excep-