· Learn vocabulary
· Explain the importance of error handling in a program
· Understand the different types of data errors that should be tested
· Describe the different methods of trapping and handling errors
In the ideal world, programs always receive information in exactly the correct format. If the ideal world all methods get the information they are written to handle and never get send information that they are not ready to handle. In short, in the ideal world nothing ever goes wrong. Unfortunately, we do not live in an ideal world. Users enter numbers where letters are expected. Programmers pass large numbers to methods that can only handle small numbers. A method may expect parameters in one order and a programmer tries to pass them in a different order. For all these reasons, good programs must be prepared to handle unexpected inputs and results.
If a program is not written to handle errors there are several possible results that can take place when errors happen. In some cases, the program will just stop working. The program will crash. You have probably seen cases where a program crashed and a message such as "invalid exception" is displayed. Those are cases where the program was not setup to handle a problem. The other possibility is that the program will continue running but with bad data. This will result in incorrect results at some point in the execution.
The bad news is that a program crashing is the better option. At least if the program crashes someone knows there is a problem. There have been cases where incorrect data in a payroll program was not caught and someone was paid an additional $100,000. There have been a number of cases where network programs did not check the size of the data being passed and hackers were able to gain illegal access to networks. The can be a huge price to pay for unchecked erroneous data.
In this chapter, you will learn about some different types of errors that programs should catch. You will also learn about different ways that programs can handle the errors they do catch.
Programs are susceptible to many types of errors but most of them fall into a small number of general types. Logic errors are errors in the logic of the program itself. Data errors are errors caused by the correct logic being applied to the wrong data. Error handling in this chapter is most concerned with catching the data error. There are times when good data checking can also help catch and identify logic errors. Programmers often add extra error checks into code under development to catch bad data that is generated by incorrect logic.
Many data errors are caused by values that are out of range. This means that a number is presented that is higher or lower then the expected maximum or minimum value. For example, a method that was going to process based on age would be confused at best when presented with an age of -1. A method processing scheduling would have problems with month 13 or day 31 in February. You probably remember hearing about the concern over programs that might not recognize years after 1999 a few years ago. This range error, called the Year 2000 bug, was caused by programs that expected two digit years and could not handle years beyond 99. These are range errors.
Type errors are often caused by data of the wrong type being presented. If a program requested an age and the user answered with a name this would cause the program to malfunction because strings do not copy into integers.
Overflow errors are related to type errors. An overflow error is cause by giving a section of memory more information then it is setup to handle. This is sometimes explained by someone trying to put ten pounds of sugar in a 5-pound bag. Sugar or, in the case of a program, information is lost.
Return codes are the traditional way of handling errors. A return code is a value that a method returns to the calling method that indicates if the method was able to function correctly. The return code could be a Boolean value that is set to true if the method was successful and false if it were not successful. A method that has many different possibilities for problems might return any of a number of codes that each indicates a different problem. For example, the following method calculates an average from a total and a count. If the total is zero or the count is zero the calculation will result in an error. A total of zero will always give an average of zero so there is not point in calculating one. A count of zero will cause the program to crash because dividing by zero is an illegal operation. The method below uses a different return code to tell a calling method which value was wrong. If both values are in the correct range, the answer is returned as the return code.
public static int CalcAvg(int total, int cnt)
{
if (cnt <= 0)
{
return -1;
}
else
return (total/cnt);
}
The calling method must have code to check the return value, such as the following.
int myAvg = CalcAvg(myTotal, myCnt);
switch (myAvg)
{
case -1:
Console.WriteLine("Invalid Count {0}",myCnt);
break;
default:
Console.WriteLine("Average = {0}",myAvg);
break;
}
Methods can be called without saving or checking the return value. If the calling method does not check the return value, it is lost and problems will show up in later processing. When these other problems occur it will be difficult to determine where things went wrong.
C# provides another method of handling errors that makes it easy for a method to handle errors locally and harder for errors to be missed then using a return code. This process is called try/catch.
The idea behind the try/catch method of error handling is that your program tries an action that is susceptible to errors inside a special block of code. If the program has an error at runtime, the system will throw an exception. The catch code will then handle the problem that has occurred.
The structure of the try and catch code starts with the reserved word try followed by a block of code inside curly braces. The reserved word catch and its block of code follow the try block. The following code divides two numbers inside a try block. If an exception is thrown, divide by zero being one possibility, the catch block will execute.
int avg;
try
{
avg = total/cnt;
}
catch (Exception e)
{
Console.WriteLine("{0}", e.Message);
}
This catch statement accepts the Exception class and calls this exception e. The exception class has a string property called Message what can be displayed. This catch block displays the error message on the console.
If no problems develop inside the try block then the catch block and its code are ignored. The program will continue with the code after the catch block after the try block executes successfully or after the catch block executes if an exception is caught.
There are a number of exception classes that are already created for C#. These exceptions are all derived from the basic system exception class. Some of the common exceptions are named and explained in table 8-x Common Exceptions. One of these is called DivideByZeroException. This means that you can create a catch block that just catches divide by zero exceptions. You can write several catch blocks using named exceptions to handle specific errors. A final catch block that uses the base Exception class will handle any unplanned for errors. If no catch block exists for the exception that is thrown then it will pass back to the class that called the called the class with the error. This calling class should catch and handle the error. If no class catches the exception, the program will fail and messages will be displayed on the console.
The following code demonstrates catching a specific exception with a second catch block for any additional exceptions.
int avg;
try
{
avg = total/cnt;
}
catch (DivideByZeroException e)
{
Console.Write("Attempted to divide {0} by zero", total);
}
catch (Exception e)
{
Console.WriteLine("Unexpected error: {0}",e.Message);
}
It is a good idea to display a message that will identify the error and the variables that may be related to the exception being thrown.
|
Exception Class |
Description |
|
SystemException |
This is the base class for all other exceptions. |
|
ArguementOutOfRangeException |
The argument passed to a method is either too high or too low for the action requested. |
|
ArithmeticException |
This exception occurs when a number is computed that cannot be represented in the result type or has an infinite value. |
|
ArrayTypeMismatchException |
This exception is thrown when a program attempts to copy the wrong type of object into an array. |
|
DivideByZero |
Dividing a number by zero is an invalid operation. |
|
IndexOutOfRangeException |
This is the exception thrown when a program attempts to access an element of an array that does not exist. |
|
An arithmetic operation has resulted in a number that is too large to fit in the type. |
|
|
RankException |
An array with the wrong number of dimensions is passed to a method. For example, a double dimension array where a single dimension array is expected. |
Table 8-x Common Exceptions
All exceptions are derived from the SystemException class. Some exceptions are derived from other exceptions in a hierarchical structure. For example, the DivideByZero exception is derived from the ArithmeticException. This is important to understand when arraigning several catch blocks. The first catch block that matches and exception will process it. Because a DivideByZero exception is derived from the ArithmeticException a catch block for an ArithmeticException will match a divide by zero. The more general exception, ArithmeticException in this case, should follow the catch block for the more specific exception. If this is not done then the more general exception catch will mask the specific exception handler.
There are times when a program should do some end of task processing in spite of errors that may have occurred. This is common when data is entered in a text box. Once the information in the box has been processed, a program will often want to clear the box and get it ready for the next input. The finally block is an optional part of the try/catch structure that lets a programmer specify clean up processing.
The following code demonstrates a finally block.
try
{
int flip = Convert.ToInt32(textBox1.Text);
Array.Reverse(iBox,0,flip);
ReDrawBoxes();
}
}
catch (Exception e1)
{
MessageBox.Show(this,e1.Message);
}
finally
{
textBox1.Text = "";
textBox1.Focus();
}
}
This code takes the value in a text box and converts it into an integer. This integer value is then used to reverse the contents of an array. There are two obvious places in this code for data entry errors to take place. If a value is entered in the textbox that is not convertible into an integer the ToInt32 method will throw an exception. If the integer in flip is a negative value or if it is larger then the number of elements in the array then the Reverse method will throw an exception.
Either of these exceptions will be caught by the catch block and an error message will be displayed in a message box.
The finally block clears the contents of the test box and sets focus to it so that the user is returned to the location where they entered the data. This code is included in a finally block because the programmer wants to make sure that this clean up happens after both a successful and unsuccessful operation inside the try block.
The examples used so far all show the system throwing exceptions. The system throws an exception when it discovers that a program attempts to divide by zero, or address an element of an array that does not exist or any number of other problems. You may be wondering how you can use try/catch with problems that a program can recognize but that the system may not see as a problem. The answer is that programmers can write code to throw an exception.
The simplest exception to throw is a system exception with a new message. The constructor for the Exception class takes a message as a parameter. The following code shows an exception being thrown with a message.
if (cnt == 0)
{
throw (new Exception("Bad things happened"));
}
These exceptions must be caught by a catch block like other exceptions.
Good programs should check data to try to prevent problems. When problems do occur during runtime a program should be prepared to handle it. The goal of good error handling is to prevent errors that can be prevented and to minimize the problems from errors that cannot be prevented.
Methods that process data are often programmed to return different codes to indicate if they were successful or not. These return codes should always be checked before processing continues.
The try/catch form of error handling is used to identify and process errors at the location in the program where the errors are generated. The try block holds code where exceptions are possible. The catch block or blocks follow the try block and handle exceptions so that the program does not fail completely. If the program cannot continue, a message is displayed that will help a programmer correct the problem so that exceptions do not occur in the future.
The finally block is an optional part of try/catch exception handling. The finally block always executes and is used to process commands that must take place if the try block is successful or unsuccessful.
A program may throw exceptions to force error processing to take place in different parts of a program. A catch block may be able to partially process an exception but throw the same exception again so that additional processing will take place at a higher level of the program.
Exception An exception is a situation that is out of the ordinary or is unexpected.
1. Create an addition program. Accept a value from a user, from either the command line or a textbox. Catch an exception thrown when the value cannot be converted into a number. If the value entered is "Exit" exit the program. clear the running total. If the value entered is "Clear", If the value is not "Exit" or "Clear", display the total of numbers entered.