Handling Problems in Your Programs: Exceptions and Errors 299
9
provide cleaner results than pop-up boxes, terminated programs, and cryptic messages.
To do this, you use the try and catch keywords.
Using try and catch
The try and catch keywords are the key to exception handling. The try command enables
you to put a wrapper around a block of code that helps you route any problems (excep-
tions) that might occur.
The catch keyword enables you to catch the exceptions that the try command routes. By
using a
catch, you get the chance to execute code and control what happens rather than
letting the program terminate. Listing 9.2 illustrates a basic use of the try and catch com-
mands.
LISTING 9.2 TryIt.cs—Using try-catch
1: // TryIt.cs
2: // A program that throws an exception
3: //=============================================
4: using System;
5:
6: class TryIt
7: {
8: public static void Main()
9: {
10: int [] myArray = new int[5];
11:
12: try
13: {
14: for ( int ctr = 0; ctr < 10; ctr++ ) // Array only has 5 ele-
➥ments!
15: {
In Listing 9.2, the catch statement catches any exception that might occur within the try
statement’s code. In addition to generically catching thrown exceptions, you can deter-
mine which exception was thrown by including a parameter on your catch. The format of
the catch is as follows:
catch( System.Exception e ) {}
The catch statement can receive the exception as a parameter. In this example, the excep-
tion is a variable named e. You could call this something more descriptive, but for this
example, the name e works.
You can see that
e is of type System.Exception, a fully qualified name meaning that the
Exception type is defined in the System namespace. If you include the System statement
with a using statement, you can shorten the catch call to this:
catch(Exception e) {}
The Exception type variable e contains descriptive information on the specific exception
that was caused. Listing 9.3 is a modified version of Listing 9.2, containing a catch state-
ment that receives any exceptions as a parameter. The changed lines are in boldface.
LISTING 9.3 TryIt2.cs—Catching Exception Information
1: // TryIt2.cs
2: // A program that throws an exception
3: //=============================================
4: using System;
300 Day 9
ANALYSIS
Handling Problems in Your Programs: Exceptions and Errors 301
9
5:
6: class TryIt2
7: {
8: public static void Main()
9: {
try statement. The error printed
is based on the type of exception executed. You can actually add code to your program to
work with specific errors.
LISTING 9.3 continued
ANALYSIS
OUTPUT
Using Multiple catches for a Single try
The catch statement in Listing 9.2 is rather general. It can catch any exception that might
have occurred in the code within the try statement code. You can include a catch state-
ment that is more specific—in fact, you can write a catch statement for a specific excep-
tion. Listing 9.4 includes a catch statement that captures the exception you are already
familiar with—IndexOutOfRangeException.
LISTING 9.4 CatchIndex.cs—Catching a Specific Exception
1: // CatchIndex.cs
2: // A program that throws an exception
3: //=============================================
4: using System;
5:
6: class CatchIndex
7: {
8: public static void Main()
9: {
10: int [] myArray = new int[5];
11:
12: try
13: {
14: for ( int ctr = 0; ctr < 10; ctr++ ) // Array only has 5 ele-
➥ments!
15: {
16: myArray[ctr] = ctr;
parameter for a general exception, the catch statement in Line 20 has a parameter for an
IndexOutOfRangeException type. Like the general Exception type, this is in the System
namespace. Just as its name implies, this exception type is specifically for indexes that
go out of range. This catch statement captures only this type of exception, though.
To be prepared for other exceptions that might occur, a second
catch statement is
included in Lines 25–28. This catch includes the general Exception type parameter, so it
will catch any other exceptions that might occur. Replace Line 16 of Listing 9.4 with the
following:
16: myArray[ctr] = 100/ctr; // division by zero
When you recompile and run the program, you will get the following output:
Exception caught: System.DivideByZeroException: Attempted to divide by zero.
at CatchIndex2.Main()
Done with the catch statements. Done with program.
The new Line 16 causes a different error. The first time through the for loop in Line 14,
ctr is equal to 0. Line 16 ends up dividing 100 by 0 (ctr). Division by 0 is not legal
because it creates an infinite number, and thus an exception is thrown. This is not an
index out of range, so the catch statement in Line 20 is ignored because it doesn’t match
the IndexOutOfRangeException type. The catch in Line 25 can work with any exception and
thus is executed. Line 27 prints the statement “Exception Caught”, followed by the excep-
tion description obtained with the variable e. As you can see by the output, the exception
thrown is a DivideByZeroException.
Understanding the Order of Handling Exceptions
In Listing 9.4, the order of the two catch statements is very important. You always
include the more specific exceptions first and the most general exception last. Starting
with the original Listing 9.4, if you change the order of the two catch statements:
LISTING 9.4 continued
OUTPUT
ANALYSIS
catch (Exception e)
15: {
16: myArray[ctr] = ctr;
17: }
18: }
19:
20: // catch
21: // {
22: // Console.WriteLine(“Exception caught”);
23: // }
24:
25: finally
304 Day 9
Handling Problems in Your Programs: Exceptions and Errors 305
9
26: {
27: Console.WriteLine(“Done with exception handling”);
28: }
29:
30: Console.WriteLine(“End of Program”);
31: }
32: }
Unhandled Exception: System.IndexOutOfRangeException: Index was outside
➥ the bounds of the array.
.IndexOutOfRangeException was thrown.
at Final.Main()
Done with exception handling
Listing 9.5 is the same listing you saw before. The key change to this listing is
in Lines 25–28: A finally clause has been added. In this listing, the finally
clause prints a message. It is important to note that even though the exception was not
caught by a catch clause (Lines 20–23 are commented out), the finally still executed
6:
7: class ListFile
8: {
9: public static void Main(string[] args)
10: {
11: try
12: {
13:
14: int ctr=0;
15: if (args.Length <= 0 )
16: {
17: Console.WriteLine(“Format: ListFile filename”);
18: return;
19: }
20: else
21: {
22: FileStream fstr = new FileStream(args[0], FileMode.Open);
23: try
24: {
25: StreamReader sReader = new StreamReader(fstr);
26: string line;
27: while ((line = sReader.ReadLine()) != null)
28: {
29: ctr++;
30: Console.WriteLine(“{0}: {1}”, ctr, line);
31: }
32: }
33: catch( Exception e )
34: {
35: Console.WriteLine(“Exception during read/write: {0}\n”, e);
6:
7: class ListFile
8: {
9: public static void Main(string[] args)
10: {
11: try
12: {
13:
14: int ctr=0;
15: if (args.Length <= 0 )
16: {
17: Console.WriteLine(“Format: ListFile filename”);
18: return;
19: }
20: else
21: {
22: FileStream fstr = new FileStream(args[0], FileMode.Open);
23: try
24: {
25: StreamReader sReader = new StreamReader(fstr);
26: string line;
27: while ((line = sReader.ReadLine()) != null)
28: {
29: ctr++;
30: Console.WriteLine(“{0}: {1}”, ctr, line);
31: }
32: }
33: catch( Exception e )
34: {
35: Console.WriteLine(“Exception during read/write:
dling.
This listing incorporates everything you’ve been learning. In Lines 4–5, you see that not
only is the
System namespace being used, but so is the IO namespace within System. The
IO namespace contains routines for sending and receiving information (input/output).
In Line 7, you see the start of the main application class,
ListFile. This class has a Main
routine, where program execution starts. In Line 9, the Main method receives a string
array named args as a parameter. The values within args are obtained from the com-
mand-line arguments that you include when you run the program.
Line 11 starts the code that is the focus of today’s lesson. In this line, a
try block is
declared. This try block encompasses the code from Line 11 to Line 42. You can see that
this try block has lots of code in it, including another try command. If any of the code
within this try block causes an exception to occur—and not be handled— the try state-
ment fails and control goes to its catch blocks. It is important to note that only unhandled
exceptions within this try block cause flow to go to this try’s catch statements.
308 Day 9
Handling Problems in Your Programs: Exceptions and Errors 309
9
Two catch blocks are defined for this overriding try statement. The first, in Lines 44–47,
catches a specific exception, FileNotFoundException. For clarity’s sake, the exception
name is fully qualified; however, you could have chosen to shorten this to just the excep-
tion type because System.IO was included in Line 5. The FileNotFoundException occurs
when you try to use a file that does not exist. In this case, if the file doesn’t exist, a sim-
ple message is printed in Line 46 that states the file couldn’t be found.
Although the
FileNotFoundException is expected with this program, Lines 48–51 were
added in case an unexpected exception happens. This allows a graceful exit instead of
relying on the runtime.
Line 26 contains a string variable named
line, which is used to hold a group of charac-
ters that are being streamed into your program. In Line 27, you see how line is used.
Line 27 does a lot, so it is worth dissecting. First, a line of characters is streamed into
your program using sReader. The StreamReader type has a method named ReadLine that
provides a line of characters. A line of characters is all the characters up until a newline
character is found. Because t was associated with fstr and fstr is associated with the
file the reader entered, the ReadLine method returns the next line of characters from the
user’s file. This line of characters is then assigned to the line string variable. After read-
ing this line of characters and placing it into the line variable, the value is compared to
null. If the string returned was null, it was either the end of the file or a bad read. Either
way, there is no reason to continue processing the file after the null value is encountered.
If the characters read and placed into
line are not equal to null,thewhile statement
processes its block commands. In this case, the line counter, ctr, is incremented and the
line of text is printed. The printing includes the line number, a colon, and the text from
the file that is in the line variable. This processing continues until a null is found.
If anything goes wrong in reading a line of the file, an exception most likely is thrown.
Lines 33–36 catch any exceptions that might occur and add additional descriptive text to
the exception message. This
catch prevents the runtime from taking over. Additionally, it
helps you and your users by giving additional information on where the error occurred.
Lines 37–40 contain a
finally that is also associated with the try in Line 23. This
finally does one thing: It closes the file that was opened in Line 22. Because Line 22
was successful—if it had not been successful, it would have tossed an exception and pro-
gram flow would have gone to Line 44’s catch statement—the file needs to be closed
before the program ends. Whether an exception occurs in Lines 24–32 or not, the file
should still be closed before leaving the program. The finally clause makes sure that the
Close method is called.
Caused by an attempt to divide by zero.
FormatException Format is incorrect.
An argument has the wrong format.
IndexOutOfRangeException Index is out of range.
Caused when an index is used that is less than
0 or higher
than the top value of the array’s index.
InvalidCastException Invalid cast. This is caused when an explicit conversion
fails.
MulticastNotSupportedException Multicast not supported. This is caused when the combina-
tion of two non-null delegates fails. (Delegates are covered
on Day 13, “Making Your Programs React with Delegates,
Events, and Indexers.”)
NotFiniteNumberException Not a finite number. The number is not valid.
NotSupportedException Method is not supported. This indicates that a method is
being called that is not implemented within the class.
NullReferenceException Reference to null. This is caused when you refer to a refer-
ence object that is null.
OutOfMemoryException Out of memory. This is caused when memory is not avail-
able for a new statement to allocate.
OverflowException Overflow. This is caused by a math operation that assigns a
value that is too large (or too small) when the checked key-
word is used.
StackOverflowException Stack overflow. This is caused when too many commands
are on the stack.
TypeInitializationException Bad type initialization. This is caused when a static con-
structor has a problem.
312 Day 9
TABLE 9.1 continued
Exception Name Description
4: //===============================================
5: using System;
6:
7: class Zero
8: {
9: public static void Main()
10: {
11: Console.WriteLine(“Before Exception ”);
12: throw( new DivideByZeroException() );
13: Console.WriteLine(“After Exception ”);
14: }
15: }
Before Exception
Unhandled Exception: System.DivideByZeroException: Attempted to divide
➥by zero.
at Zero.Main()
This listing does nothing other than print messages and throw a
DivideByZeroException exception in Line 12. When this program executes, you
get a runtime error that indicates the exception was thrown. It’s simple but impractical.
When you compile this listing, you get a compiler warning:
Zero.cs(13,7): warning CS0162: Unreachable code detected
This is because Line 13 will never be executed: The throw command terminates the pro-
gram. Remember, a throw command leaves the current routine immediately. You can
remove Line 13 from the listing—because it would never execute anyway—to avoid the
compiler warning. It was added to this listing to emphasize what an exception does to
program flow.
The use of parentheses with the throw keyword is optional. The following
two lines are the equivalent:
throw( exception );
throw exception;
10: public static void Main()
11: {
12: int result;
13:
14: try
15: {
16: result = MyMath.AddEm( 1, 2 );
17: Console.WriteLine( “Result of AddEm(1, 2) is {0}”, result);
18:
19: result = MyMath.AddEm( 3, 4 );
Handling Problems in Your Programs: Exceptions and Errors 315
9
20: Console.WriteLine( “Result of AddEm(3, 4) is {0}”, result);
21: }
22:
23: catch (MyThreeException)
24: {
25: Console.WriteLine(“Ack! We don’t like adding threes.”);
26: }
27:
28: catch (Exception e)
29: {
30: Console.WriteLine(“Exception caught: {0}”, e);
31: }
32:
33: Console.WriteLine(“\nAt end of program”);
34: }
35: }
36:
37: class MyMath
MyThreeException. Line 23 contains a catch statement that is looking for a
MyThreeException and thus catches and takes care of it.
If you don’t catch the exception, the runtime throws an exception message for you. If
you comment out Lines 23–26 of Listing 9.8, you get output similar to the following
when you compile and rerun the program:
Result of AddEm(1, 2) is 3
Exception caught: MyThreeException: Exception of type MyThreeException was
➥thrown.
at MyMath.AddEm(Int32 x, Int32 y)
at MathApp.Main()
At end of program
This is the same type of message that any other exception receives. You can also pass a
parameter to the catch class that handles your exception. This parameter contains the
information for the general system message. For example, change Lines 23–26 to the fol-
lowing:
23: catch (MyThreeException e )
24: {
25: Console.WriteLine(“Ack! We don’t like adding threes. \n {0}” ,
➥e);
26: }
You will see the following results (this assumes that you uncommented the lines as well):
Result of AddEm(1, 2) is 3
Ack! We don’t like adding threes.
MyThreeException: An exception of type MyThreeException was thrown.
at MathApp.Main()
At end of program
Your new exception is as fully functioning as any of the existing exceptions.
316 Day 9
Listing 9.8 creates a basic exception. To be more complete, you should
include three constructors for your new exception. The details of these over-
catch statement. This para-
meter should be of the error type. The following code illustrates how the generic catch
statement could rethrow an exception that was caught:
catch (Exception e)
{
// My personal exception logic here
}
public MyThreeException( string e ) : base (e)
{
}
public MyThreeException( string e, Exception inner ) :
base ( e, inner )
{
}
}
You can replace the exception name of MyThreeException with your own
exception.
throw ( e ); // e is the argument received by this catch
}
318 Day 9
As you begin to build more detailed applications, you might want to look
deeper into exception handling. You have learned the most important fea-
tures of exception handling today, but you can do a lot more with them.
Such topics are beyond the scope of this book, however.
Note
Using checked Versus unchecked Statements
Two additional C# keywords can make an impact on exceptions being thrown. These are
checked and unchecked. If the code is checked and a value is placed in a variable that is
too big or too small, an exception will occur. If the code is unchecked, the value placed
will be truncated to fit within the variable. Listing 9.9 illustrates these two keywords in
2147483645 assigned from 2147483645
2147483646 assigned from 2147483646
2147483647 assigned from 2147483647
Unhandled Exception: System.OverflowException: Arithmetic operation
resulted in an overflow.
at CheckIt.Main()
In Line 11 of this listing, a variable named topval is created as a constant vari-
able that contains the largest value that a regular integer variable can hold,
2147483647. The for loop in Line 13 loops to a value that is 10 higher than this top value.
This is being placed in a long variable, which is okay. In Line 17, however, the ctr value
is being explicitly placed into result, which is an integer. When you execute this listing,
you receive an error because the code in Lines 16–19 is checked. This code tries to assign
a value to result that is larger than the largest value it can hold.
OUTPUT
ANALYSIS
If you remove the +10 from Line 13 of the listing and compile it, you will see
that the listing works. This is because there is nothing wrong. It is when you
try to go above the topVal that the overflow error occurs.
Note
You should now change this listing to use the unchecked keyword. Change Line 15 in the
listing to the following:
13: unchecked
Recompile and execute the listing. The listing will compile this time; however, the output
might be unexpected results. The output this time is as follows:
2147483642 assigned from 2147483642
2147483643 assigned from 2147483643
2147483644 assigned from 2147483644
2147483645 assigned from 2147483645
2147483646 assigned from 2147483646
2147483647 assigned from 2147483647
gram, it is up to you to determine what the problem is. In small programs such as those
used as examples in this book, it is usually relatively easy to look through the listing to
figure out what the problem is. In larger programs, finding the error can be much harder.
The process of looking for and removing an error is called debugging. An error
is often referred to as a bug in a program. One of the first computer problems
was caused by a bug—specifically, a moth. This bug was found in the computer and
removed. Although this error was caused by an actual bug, it has become common to
refer to all computer errors as bugs.
NEW TERM
Handling Problems in Your Programs: Exceptions and Errors 321
9
Understanding the Types of Errors
As you learned on one of the first days of this book, a number of different types
of errors exist. Most errors must be caught before you can run your program.
The compiler catches these problems and lets you know about them in the form of errors
and warnings. Other errors are harder to find. For example, you can write a program that
compiles with no errors but that doesn’t perform as you expect. These errors are called
logic errors. You can also cleanly compile a listing but run into errors when the end user
enters bad information, when data is received that your program does not expect, when
data is missing, or when any of a nearly infinite number of things is not quite right.
Finding Errors
You will find two standard types of errors: syntax errors and runtime errors.
Encountering Syntax Errors
Syntax errors are generally identified when you compile your listing. At compile time,
the compiler identifies these problems with errors and warnings. The compiler provides
the location of these errors with a description.
Encountering Runtime Errors
Runtime errors are caused by several issues. You’ve learned to prevent some of these
from crashing your programs by adding exception handling to your programs. For exam-
ple, if a program tries to open a file that doesn’t exist, an exception is thrown. By adding
NEW TERM
Many companies have code walkthroughs as a standard part of the develop-
ment process. Generally, these involve sitting down with one or more other
people on a project and reading through the code together. It is your job in
these walkthroughs to explain the code to the other participants. You might
think that there is little value to this; however, often you will find better
ways to complete the same task.
Note
Working with Preprocessor Directives
C# provides a number of directives that can be used within your code. These directives
can determine how the compiler treats your code. If you have programmed in C or C++,
you might be familiar with directives such as these. In C#, however, there are fewer
directives. Table 9.2 presents the directives available in C#. The following sections cover
the more important of these.