Basic Error Handling with Exceptions


An important aspect of calling methods relates to error handling; specifically, how to report an error back to the caller. This section examines how to handle error reporting via a mechanism known as exception handling.

With exception handling, a method is able to pass information about an error to a calling method without explicitly providing any parameters to do so. Listing 4.17 contains a slight modification to the HeyYou program from Chapter 1. Instead of requesting the last name of the user, it prompts for the user's age.

Listing 4.17. Converting a string to an int

 using System; class ExceptionHandling {   static void Main()   {       string firstName;       string ageText;       int age;       Console.WriteLine("Hey you!");       Console.Write("Enter your first name: ");       firstName = System.Console.ReadLine();       Console.Write("Enter your age: ");                                                        ageText = Console.ReadLine();                                                             age = int.Parse(ageText);                              Console.WriteLine(                                                                            "Hi {0}! You are {1} months old.",                                                        firstName, age*12);                                                                } } 

Output 4.11 shows the results of Listing 4.17.

Output 4.11.

 Hey you! Enter your first name: Inigo Enter your age: 42 Hi Inigo! You are 504 months old. 

The return value from System.Console.ReadLine() is stored in a variable called ageText and is then passed to a method on the int data type, called Parse(). This method is responsible for taking a string value that represents a number and converting it to an int type.

Beginner Topic: 42 as a String Versus 42 as an Integer

C# is a strongly typed language. Therefore, not only is the data value important, but the type associated with the data is important as well. A string value of 42, therefore, is distinctly different from an integer value of 42. The string is composed of the two characters 4 and 2, whereas the int is the number 42.


Given the converted string, the final System.Console.WriteLine() statement will print the age in months by multiplying the age value by 12.

However, what happens if the user does not enter a valid integer string? For example, what happens if the user enters "forty-two"? The Parse() method cannot handle such a conversion. It expects the user to enter a string that contains only digits. If the Parse() method is sent an invalid value, it needs some way to report this fact back to the caller.

Trapping Errors

To indicate to the calling method that the parameter is invalid, int.Parse() will throw an exception. Throwing an exception will halt further execution in the current program flow and instead will jump into the first code block within the call stack that handles the exception.

Since you have not yet provided any such handling, the program reports the exception to the user as an unhandled exception. Assuming there is no registered debugger on the system, the error will appear on the console with a message such as that shown in Output 4.12.

Output 4.12.

 Hey you! Enter your first name: Inigo Enter your age: forty-two Unhandled Exception: System.FormatException: Input string was         not in a correct format.     at System.Number.ParseInt32(String s, NumberStyles style,         NumberFormatInfo info)     at ExceptionHandling.Main() 

Obviously, such an error is not particularly helpful. To fix this, it is necessary to provide a mechanism that handles the error, perhaps reporting a more meaningful error message back to the user.

This is known as catching an exception. The syntax is demonstrated in Listing 4.18, and the output appears in Output 4.13.

Listing 4.18. Catching an Exception

 using System; class ExceptionHandling {   static int Main()   {       string firstName;       string ageText;       int age;       int result = 0;       Console.Write("Enter your first name: ");       firstName = Console.ReadLine();       Console.Write("Enter your age: ");       ageText = Console.ReadLine();       try       {           age = int.Parse(ageText);           Console.WriteLine(               "Hi {0}! You are {1} months old.",               firstName, age*12);        }       catch (FormatException )       {           Console.WriteLine(               "The age entered, {0}, is not valid.",               ageText);           result = 1;       }       catch(Exception exception)       {           Console.WriteLine(               "Unexpected error: {0}", exception.Message);           result = 1;       }       finally       {           Console.WriteLine("Goodbye {0}",           firstName);       }       return result;    } } 

Output 4.13.

 Enter your first name: Inigo Enter your age: forty-two The age entered, forty-two, is not valid. Goodbye Inigo 

To begin, surround the code that could potentially throw an exception (age = int.Parse()) with a try block. This block begins with the try keyword. It is an indication to the compiler that the developer is aware of the possibility that the code within the block could potentially throw an exception, and if it does, then one of the catch blockswill attempt to handle the exception.

One or more catch blocks (or the finally block) must appear immediately following a try block. The catch block header (see the Expert Topic titled Generic catch, later in this chapter) optionally allows you to specify the data type of the exception, and as long as the data type matches the exception type, the catch block will execute. If, however, there is no appropriate catch block, the exception will fall through and go unhandled as if there were no exception handling.

The resulting program flow appears in Figure 4.1.

Figure 4.1. Exception Handling Program Flow


For example, assume the user enters "forty-two" for the age. In this case, int.Parse() will throw an exception of type System.FormatException, and control will jump to the set of catch blocks. (System.FormatException indicates that the string was not of the correct format to be parsed appropriately.) Since the first catch block matches the type of exception that int.Parse() threw, the code inside this block will execute. If a statement within the try block throws a different exception, then the second catch block would execute because virtually all exceptions are of type System.Exception.

If there were no System.FormatException catch block, then the System.Exception catch block would execute even though int.Parse throws a System.FormatException. This is because a System.FormatException is also of type System.Exception. (System.FormatException is a more specific identification of the generic exception, System.Exception.)

Although the number of catch blocks varies, the order in which you handle exceptions is significant. Catch blocks must appear from most specific to least specific. The System.Exception data type is least specific and therefore it appears last. System.FormatException appears first because it is the most specific exception that Listing 4.18 handles.

Regardless of whether the code in the try block throws an exception, the finally block of code will execute. The purpose of the finally block is to provide a location to place code that will execute regardless of how the try/catch blocks exitwith or without an exception. Finally blocks are useful for cleaning up resources regardless of whether an exception is thrown. In fact, it is possible to have a try block with a finally block and no catch block. The finally block executes regardless of whether the try block throws an exception or whether a catch block is even written to handle the exception. Listing 4.19 demonstrates the try/finally block and Output 4.14 shows the results.

Listing 4.19. Catching an Exception

 using System; class ExceptionHandling {   static int Main()   {       string firstName;       string ageText;       int age;       int result = 0;       Console.Write("Enter your first name: ");       firstName = Console.ReadLine();       Console.Write("Enter your age: ");       ageText = Console.ReadLine();       try       {           age = int.Parse(ageText);           Console.WriteLine(               "Hi {0}! You are {1} months old.",               firstName, age*12);        }       finally       {       Console.WriteLine("Goodbye {0}",           firstName);    }    return result;  } } 

Output 4.14.

 Enter your first name: Inigo Enter your age: forty-two 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 ExceptionHandling.Main() Goodbye Inigo 

When this code executes, the finally block executes before displaying an unhandled exception to the user.

Advanced Topic: Exception Class Inheritance

All exceptions derive from System.Exception. Therefore, they can be handled by the catch(System.Exception exception) block. It is preferable, however, to include a catch block that is specific to the most derived type (System.FormatException, for example), because then it is possible to get the most information about an exception and handle it less generically. In so doing, the catch statement that uses the most derived type is able to handle the exception type specifically, accessing data related to the exception thrown, and avoiding conditional logic to determine what type of exception occurred.

This is why C# enforces that catch blocks appear from most derived to least derived. For example, a catch statement that catches System.Exception cannot appear before a statement that catches System.FormatException because System.FormatException derives from System.Exception.


A method could throw many exception types. Table 4.2 lists some of the more common ones.

Table 4.2. Common Exception Types

Exception Type

Description

System.Exception

A generic exception from which other exceptions derive

System.ArgumentException

A means of indicating that one of the parameters passed into the method is invalid

System.ArgumentNullException

Indicates that a particular parameter is null and that this is not valid for that parameter

System.ApplicationException

A custom application exception that developers can use to indicate special application errors that are not fatal

System.FormatException

Indicates that the string format is not valid for conversion

System.IndexOutOfRangeException

Indicates that an attempt was made to access an array element that does not exist

System.InvalidCastException

Indicates that an attempt to convert from one data type to another was not a valid conversion

System.NotImplementedException

Indicates that although the method signature exists, it has not been fully implemented

System.NullReferenceException

Thrown when code tries to access a variable that does not yet contain any data

System.ArithmeticException

Indicates an invalid math operation, not including divide by zero

System.ArrayTypeMismatchException

Occurs when attempting to store an element of the wrong type into an array

System.StackOverflowException

Generally indicates that there is an infinite loop in which a method is calling back into itself (known as recursion)


Advanced Topic: Generic catch

It is possible to specify a catch block that takes no parameters, as shown in Listing 4.20.

Listing 4.20. General Catch Blocks

[View full width]

 ... try {     age = int.Parse(ageText);     System.Console.WriteLine(         "Hi {0}! You are {1} months old.",         firstName, age*12);  } catch (System.FormatException exception) {     System.Console.WriteLine(         "The age entered ,{0}, is not valid.",         ageText);     result = 1; } catch(System.Exception exception) {     System.Console.WriteLine(         "Unexpected error: {0}", exception.Message);     result = 1; } catch                                                       {                                                                                           System.Console.WriteLine(                                         "Unexpected error!");                                                  result = 1;                                                            }                                                                          finally {     System.Console.WriteLine("Goodbye {0}",         firstName); } ... 

A catch block with no data type, called a generic catch block, is equivalent to specifying a catch block that takes an object data type: for instance, catch(object exception){...}. And since all classes ultimately derive from object, a catch block with no data type must appear last.

Generic catch blocks are rarely used because there is no way to capture any information about the exception. In addition, C# doesn't support the ability to throw an exception of type object. (Only libraries written in languages like C++ allow exceptions of any type.)

The behavior in C# 2.0 varies slightly from the earlier C# behavior. In C# 2.0, if a language allows non-System.Exceptions, the object of the thrown exception will be wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException which does derive from System .Exception. Therefore, all exceptions, whether deriving from System.Exception or not, will propagate into C# assemblies as derived from System.Exception.

The result is that System.Exception catch blocks will catch all exceptions not caught by earlier blocks, and a general catch block, following a System.Exception catch block, will never be invoked. Because of this, following a System.Exception catch block with a general catch block in C# 2.0 will result in a compiler warning indicating the general catch block will never execute.


Reporting Errors Using a throw Statement

Just as int.Parse() can throw an exception, C# allows developers to throw exceptions from their code, as demonstrated in Listing 4.21 and Output 4.15.

Listing 4.21. Throwing an Exception

Output 4.15.

 Begin executing Throw exception... Unexpected error: Arbitrary exception Shutting down... 

As the arrows in Listing 4.21 depict, throwing an exception jumps execution from where the exception is thrown into the first catch block within the stack that is compatible with the thrown exception type. In this case, the second catch block handles the exception and writes out an error message. In Listing 4.21, there is no finally block, so execution falls through to the System.Console.WriteLine() statement following the try/catch block.

In order to throw an exception, it is necessary to have an instance of an exception. Listing 4.21 creates an instance using the keyword new followed by the data type of the exception. Most exception types allow a message as part of throwing the exception so that when the exception occurs, the message can be retrieved.

Sometimes a catch block will trap an exception but be unable to handle it appropriately or fully. In these circumstances, a catch block can rethrow the exception using the throw statement without specifying any exception, as shown in Listing 4.22.

Listing 4.22. Rethrowing an Exception

 ...        catch(Exception exception)        {            Console.WriteLine(            "Rethrowing unexpected error: {0}",            exception.Message);        throw;    } ... 

Avoid Using Exception Handling to Deal with Expected Situations

As with most languages, C# incurs a performance hit when throwing an exception, especially the first time the error-handling infrastructure needs to be loaded. For example, running Listing 4.18 and entering an invalid age demonstrates a noticeable pause while the program throws and handles the exception. Because of the performance cost associated with throwing exceptions, developers should make an effort to avoid throwing exceptions for expected conditions or normal control flow. For example, developers should expect users to enter invalid text when specifying their age.[2] Therefore, instead of relying on an exception to validate data entered by the user, developers should provide a means of checking the data before attempting the conversion. Better yet, you should prevent the user from entering invalid data in the first place.

[2] In general, developers should expect their users to perform unexpected actions, and therefore they should code defensively to handle "stupid user tricks."

Advanced Topic: Numeric Conversion with TRyParse()

One of the problems with the Parse() method is that the only way to determine whether the conversion will be successful is to attempt the cast and then catch the exception if it doesn't work. Because throwing an exception is a relatively expensive operation, it is better to attempt the conversion without exception handling. In the first release of C#, the only data type that enabled this was a double method called double.TryParse(). However, the CLI added this method to all numeric primitive types in the CLI 2.0 version. It requires the use of the out keyword because the return from the tryParse() function is a bool rather than the converted value. Here is a code listing that demonstrates the conversion using int.TryParse().

 ... if (int.TryParse(ageText, out age)) {     System.Console.WriteLine(         "Hi {0}! You are {1} months old.", firstName,         age * 12); } else {     System.Console.WriteLine(         "The age entered ,{0}, is not valid.", ageText); } ... 


With the tryParse() method, it is no longer necessary to include a try/catch block simply for the purpose of handling the string-to-numeric conversion.





Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net