Error Handling


The first part of this chapter has dealt with finding and correcting errors during application development so that they won't occur in release level code. There are times, however, when you know that errors are likely to occur and there is no way of being 100 percent sure that they won't. In these situations, it may be preferable to anticipate problems and write code that is robust enough to deal with these errors gracefully, without interrupting execution.

Error handling is the name for all techniques of this nature, and here you look at exceptions and how you can deal with them.

Exceptions

An exception is an error generated either in your code or in a function called by your code that occurs at runtime. The definition of error here is more vague than it has been up until now, because exceptions may be generated manually in functions and so on. For example, you might generate an exception in a function if one of its string parameters doesn't start with the letter a. This isn't strictly speaking an error outside of the context of this function, although it is treated as one by the code that calls the function.

You've come across exceptions a few times already in this book. Perhaps the simplest example is attempting to address an array element that is out of range, for example:

 int[] myArray = {1, 2, 3, 4}; int myElem = myArray[4]; 

This generates the following exception message and then terminates the application:

Index was outside the bounds of the array.
Note

You've seen some examples of the window that is displayed already. The window has a line connecting it to the offending code and includes links to reference topics in the VS help files as well as a View Detail... link that enables you to find out more about the exception that occurred.

Exceptions are defined in namespaces, and most have names that make it clear what they are intended for. In this example, the exception generated is called System.IndexOutOfRangeException, which makes sense because you have supplied an index that is not in the range of indices permissible in myArray.

This message only appears and the application only terminates when the exception is unhandled. So, what exactly do you have to do to handle an exception?

try . . . catch . . . finally

The C# language includes syntax for Structured Exception Handling (SEH). Keywords exist to mark code out as being able to handle exceptions, along with instructions as to what to do if an exception occurs. The three keywords you use for this are try, catch, and finally. Each of these has an associated code block and must be used in consecutive lines of code. The basic structure is as follows:

 try { ... } catch (<exceptionType> e) { ... } finally { ... } 

It is also possible, however, to have a try block and a finally block with no catch block, or a try block with multiple catch blocks. If one or more catch blocks exist, then the finally block is optional, else it is mandatory.

The usage of the blocks is as follows:

  • try: Contains code that might throw exceptions (throw is the C# way of saying generate or cause when talking about exceptions).

  • catch: Contains code to execute when exceptions are thrown. catch blocks may be set to respond only to specific exception types (such as System.IndexOutOfRangeException) using <exceptionType>, hence the ability to provide multiple catch blocks. It is also possible to omit this parameter entirely, to get a general catch block that will respond to all exceptions.

  • finally: Contains code that is always executed, either after the try block if no exception occurs, after a catch block if an exception is handled, or just before an unhandled exception terminates the application (the fact that this block is processed at this time is the reason for its existence; otherwise, you might just as well place code after the block).

The sequence of events that occurs after an exception occurs in code in a try block is:

  • The try block terminates at the point where the exception occurred.

  • If a catch block exists, then a check is made to see if the block matches the type of exception that has been thrown. If no catch block exists, then the finally block (which must be present if there are no catch blocks) executes.

  • If a catch block exists, but there is no match, then a check is made for other catch blocks.

  • If a catch block matches the exception type, the code it contains executes, and then the finally block executes if it is present.

  • If no catch blocks match the exception type, then the finally block of code executes if it is present.

Here's an example to demonstrate handling exceptions. This Try It Out throws and handles exceptions in several ways, so that you can work through the code to see how things work.

Try It Out – Exception Handling

image from book
  1. Create a new console application called Ch07Ex02 in the directory C:\BegVCSharp\Chapter7.

  2. Modify the code as follows (the line number comments shown here will help you to match up your code to the discussion afterwards, and are duplicated in the downloadable code for this chapter for your convenience):

    class Program { static string[] eTypes = {"none", "simple", "index", "nested index"}; static void Main(string[] args) { foreach (string eType in eTypes) { try { Console.WriteLine("Main() try block reached.");        // Line 23 Console.WriteLine("ThrowException(\"{0}\") called.", eType); // Line 24 ThrowException(eType); Console.WriteLine("Main() try block continues.");      // Line 26 } catch (System.IndexOutOfRangeException e)                 // Line 28 { Console.WriteLine("Main() System.IndexOutOfRangeException catch" + " block reached. Message:\n\"{0}\"", e.Message); } catch                                                     // Line 34 { Console.WriteLine("Main() general catch block reached."); } finally { Console.WriteLine("Main() finally block reached."); } Console.WriteLine(); } Console.ReadKey(); } static void ThrowException(string exceptionType) { // Line 49 Console.WriteLine("ThrowException(\"{0}\") reached.", exceptionType); switch (exceptionType) { case "none" : Console.WriteLine("Not throwing an exception."); break;                                                 // Line 54 case "simple" : Console.WriteLine("Throwing System.Exception."); throw (new System.Exception());                        // Line 57 case "index" : Console.WriteLine("Throwing System.IndexOutOfRangeException."); eTypes[4] = "error";                                   // Line 60 break; case "nested index" : try                                                    // Line 63 { Console.WriteLine("ThrowException(\"nested index\") " + "try block reached."); Console.WriteLine("ThrowException(\"index\") called."); ThrowException("index");                            // Line 68 } catch                                                  // Line 70 { Console.WriteLine("ThrowException(\"nested index\") general" + " catch block reached."); } finally { Console.WriteLine("ThrowException(\"nested index\") finally" + " block reached."); } break; } } }

  3. Run the application. The result is shown in Figure 7-22.

    image from book
    Figure 7-22

How It Works

This application has a try block in Main() that calls a function called ThrowException(). This function may throw exceptions, depending on the parameter it is called with:

  • ThrowException("none"): Doesn't throw an exception

  • ThrowException("simple"): Generates a general exception

  • ThrowException("index"): Generates a System.IndexOutOfRangeException exception

  • ThrowException("nested index"): Contains its own try block, which contains code that calls ThrowException("index") to generate a System.IndexOutOfRangeException exception

Each of these string parameters is held in the global eTypes array, which is iterated through in the Main() function to call ThrowException() once with each possible parameter. During this iteration, various messages are written to the console to indicate what is happening.

This code gives you an excellent opportunity to use the code-stepping techniques you saw earlier in this chapter. By working your way through the code a line at a time, you can see exactly how code execution progresses.

Add a new breakpoint (with the default properties) to line 23 of the code, which reads:

Console.WriteLine("Main() try block reached.");

Note

Note that I'll refer to code by line numbers as they appear in the downloadable version of this code. If you have line numbers turned off, remember that you can turn them back on through the Tools Options... menu item and the Text Editor C# General option section. Comments are included in the code shown above so that you can follow the text without having the file open in front of you.

Run the application in debug mode.

Almost immediately, the program will enter Break mode, with the cursor on line 23. If you select the Locals tab in the variable monitoring window, you should see that eType is currently "none". Use the Step Into button to process lines 23 and 24, and check that the first line of text has been written to the console. Next, use the Step Into button to step into the ThrowException() function on line 25.

Once in the ThrowException() function (on line 49), the Locals window changes. eType and args are no longer in scope (they are local to Main()); instead, you see the local exceptionType argument, which is of course "none". Keep pressing Step Into and you'll reach the switch statement that checks the value of exceptionType and execute the code that writes out the string Not throwing an exception to the screen. When you execute the break statement (on line 54), you exit the function and resume processing in Main() at line 26. Because no exception was thrown the try block continues.

Next, processing continues with the finally block. Click Step Into a few more times to complete the finally block and the first cycle of the foreach loop. The next time you reach line 25, ThrowException() is called using a different parameter, simple.

Continue using Step Into through ThrowException(), and you'll eventually reach line 57:

throw (new System.Exception());

Here you use the C# throw keyword to generate an exception. This keyword simply needs to be provided with a new-initialized exception as a parameter, and it will throw that exception. Here, you are using another exception from the System namespace, System.Exception.

Note

Note that no break; statement is necessary in this case: block – throw is enough to end execution of the block.

When you process this statement with Step Into, you find yourself at the general catch block starting on line 34. There was no match with the earlier catch block starting on line 28, so this one is processed instead. Stepping through this code takes you through this block, through the finally block, and back into another loop cycle that calls ThrowException() with a new parameter on line 25. This time the parameter is "index".

This time, ThrowException() generates an exception on line 60:

eTypes[4] = "error";

The eTypes array is global, so you have access to it here. However, here you are attempting to access the fifth element in the array (remember counting starts at 0), which generates a System.IndexOutOfRangeException exception.

This time there is a matched catch block in Main(), and stepping into the code takes you to this block, starting at line 28.

The Console.WriteLine() call in this block writes out the message stored in the exception using e.Message (you have access to the exception through the parameter of the catch block). Again, stepping through takes you through the finally block (but not the second catch block, as the exception is already handled) and back into the loop cycle, again calling ThrowException() on line 25.

When you reach the switch structure in ThrowException(), this time you enter a new try block, starting on line 63. When you reach line 68, you perform a nested call to ThrowException(), this time with the parameter "index". If you like, use the Step Over button to skip the lines of code that are executed here, because you've been through them already. As before, this call generates a System.IndexOutOfRangeException exception. However, this time the exception is handled in the nested try . . . catch . . . finally structure, the one in ThrowException(). This structure has no explicit match for this type of exception, so the general catch block (starting on line 70) deals with it.

As with the earlier exception handling, you now step through this catch block and the associated finally block, and reach the end of the function call. However, there is one crucial difference. Although an exception has been thrown, it has also been handled — by the code in ThrowException(). This means that there is no exception left to handle in Main(), so you go straight to the finally block, and after that the application terminates.

image from book

Listing and Configuring Exceptions

the .NET Framework contains a whole host of exception types, and you are free to throw and handle any of these in your own code, or even throw them from your code so that they may be caught in more complex applications. VS supplies a dialog for examining and editing the available exceptions, which can be called up with the the Debug Exceptions... menu item (or by pressing Ctrl+D, E). This dialog is shown in Figure 7-23.

image from book
Figure 7-23

Exceptions are listed by category and .NET library namespace. You can see the exceptions in the System namespace by expanding the Common Language Runtime Exceptions tab, and then the System tab. This list includes the System.IndexOutOfRangeException exception you used above.

Each exception may be configured using the check boxes shown. You can use the first option, (break when) Thrown, to cause a break into the debugger even for exceptions that are handled. The second option allows you to ignore unhandled exceptions, and suffer the consequences. In most cases, this will result in Break mode being entered in any case, so you are likely only to need to do this in exceptional circumstances.

In most cases, the default settings here are fine.

Notes on Exception Handling

Note that you must always supply catch blocks for more specific exceptions before more general catching. If you get this the wrong way round the application will fail to compile.

Note also that you can throw exceptions from within catch blocks, either in the ways used in the last example or simply by using the expression:

 throw; 

This expression results in the exception handled by the catch block being rethrown.

If you throw an exception in this way, it will not be handled by the current try . . . catch . . . finally block, but by parent code (although the finally block in the nested structure will still execute).

For example, if you changed the try . . . catch . . . finally block in ThrowException() as follows

try {    Console.WriteLine("ThrowException(\"nested index\") " +                      "try block reached.");    Console.WriteLine("ThrowException(\"index\") called.");    ThrowException("index"); } catch {    Console.WriteLine("ThrowException(\"nested index\") general"       + " catch block reached."); throw; } finally {    Console.WriteLine("ThrowException(\"nested index\") finally"       + " block reached."); }

then execution would proceed first to the finally block shown here, then with the matching catch block in Main(). The resulting console output changes, as shown in Figure 7-24.

image from book
Figure 7-24

In this screenshot, you see extra lines of output from the Main() function, as the System.IndexOutOfRangeException is caught in this function.




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

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