Writing an Error Handler Using try...Catch...FinallyIt's useful to have Visual C# halt execution when an exception occurs when debugging. When the code is halted while running in the IDE, you receive an error message, and you're shown the offending line of code. However, when your project is run as a compiled program, an unhandled exception causes the program to terminate (aka crash to the desktop). This is one of the most undesirable things an application can do. Fortunately, you can prevent exceptions from stopping code execution (and terminating compiled programs) by writing code specifically designed to deal with exceptions. Exception-handling code instructs Visual C# on how to deal with an exception instead of relying on Visual C#'s default behavior of aborting the application. Visual C# supports structured exception handling (a formal way of dealing with errors) in the form of the try block. Creating structured error-handling code can be a bit confusing at first, and like most coding principles, it's best understood by doing it. Create a new Windows Application called Structured Exception Handling and follow these steps to build the project:
As you can see, the try, catch, and finally statements use the braces ({}) to enclose statements. The TRy, catch, and finally structure is used to wrap code that may cause an exception; it provides the means of dealing with thrown exceptions. Table 15.2 explains the sections of this structure.
By the Way There are three forms of try statements:
Press F5 to run the project and then click the button. Next, take a look at the contents of the Output window. The Output window should contain the following lines of text (among others): Try Finally Done Trying Here's what happened:
Stop the project now by choosing Debug, Stop Debugging from the menu. Now that you understand the basic mechanics of the try, catch, and finally structure, you're going to add statements within the structure so that an exception occurs and gets handled. Change the contents of the procedure to match this code: long lngNumerator = 10; long lngDenominator = 0; long lngResult; try { Debug.WriteLine("Try"); lngResult = lngNumerator / lngDenominator; } catch { Debug.WriteLine("Catch"); } finally { Debug.WriteLine("Finally"); } Debug.WriteLine("Done Trying"); Again, press F5 to run the project. Click the button and take a look at the Output window. This time, the text in the Output window should read Try A first chance exception of type 'System.DivideByZeroException' occurred in Structured Exception Handling.exe Catch Finally Done Trying Notice that this time the code within the catch section executes. That's because the statement that sets lngResult causes a DivideByZero exception. Had this statement not been placed within a TRy block, Visual C# would have raised the exception, and an error dialog box would've appeared. However, because the statement is placed within the TRy block, the exception is caught. Caught means that when the exception occurred, Visual C# directed execution to the catch section. (You do not have to use a catch section. If you omit a catch section, caught exceptions are simply ignored.) Notice also how the code within the finally section executes after the code within the catch section. Remember that code within the finally section always executes, regardless of whether an exception occurs. Dealing with an ExceptionCatching exceptions so that they don't crash your application is a noble thing to do, but it's only part of the error-handling process. You'll usually want to tell the user (in a friendly way) that an exception has occurred. You'll probably also want to tell the user what type of exception occurred. To do this, you must have a way of knowing what exception was thrown. This is also important if you intend to write code to deal with specific exceptions. The catch statement enables you to specify a variable to hold a reference to an Exception object. Using an Exception object, you can get information about the exception. The following is the syntax used to place the exception in an exception object: catch (Exception variablename) Modify your catch section to match the following: catch (Exception objException) { Debug.WriteLine("Catch"); MessageBox.Show("An error has occurred: " + objException.Message); } The Message property of the Exception object contains the text that describes the specific exception that occurred. Run the project and click the button, and Visual C# displays your custom error message (see Figure 15.9). Figure 15.9. Structured exception handling enables you to decide what to do when an exception occurs.
Handling an Anticipated ExceptionAt times, you'll anticipate a specific exception being thrown. For example, you might write code that attempts to open a file when the file does not exist. In such an instance, you'll probably want the program to perform certain actions when this exception is thrown. When you anticipate a specific exception, you can create a catch section designed specifically to deal with that one exception. Recall from the previous section that you can retrieve information about the current exception using a catch statement, such as catch (Exception objException) By creating a generic Exception variable, this catch statement catches any and all exceptions thrown by statements within the try section. To catch a specific exception, change the data type of the exception variable to a specific exception type. Remember the code you wrote earlier that caused a format exception when an attempt was made to pass an empty string to the Convert.ToInt64() method? You could have used a try structure to deal with the exception, using code such as this: long lngAnswer; try { lngAnswer = 100 / long.Parse(txtInput.Text); MessageBox.Show("100/" + txtInput.Text + " is " + lngAnswer); } catch (System.FormatException) { MessageBox.Show("You must enter a number in the text box."); } catch { MessageBox.Show("Caught an exception that wasn't a format exception."); } Notice that there are two catch statements in this structure. The first catch statement is designed to catch only a format exception; it won't catch exceptions of any other type. The second catch statement doesn't care what type of exception is thrown; it catches all of them. The second catch statement acts as a catch-all for any exceptions that aren't overflow exceptions because catch sections are evaluated from top to bottom, much like case statements in the switch structure. You could add more catch sections to catch other specific exceptions if the situation calls for it. This next example, you're going to build on the Picture Viewer project last edited in Hour 11, "Using Constants, Data Types, Variables, and Arrays," so go ahead and open that project now. First, I want you to see the exception that you are going to catch. Follow these steps to cause an exception to occur:
You have now caused an Out of Memory Exception (see Figure 15.10). This is the exception thrown by the picture box when you attempt to load a file that isn't a picture. Your first reaction might be something along the lines of "why do I have to worry about that; no one would do that." Well, welcome to programming my friend! A lot of your time will be spent writing code to protect users from themselves. It's not fair and usually not fun, but it is a reality. Figure 15.10. You never want an unhandled exception to occurever.Go ahead and choose Stop Debugging on the toolbar to stop the running project. Rather than take you step by step through the changes, it will be easier to just show you the code for the new OpenPicture() procedure. Change your code to the code shown here: try { // Show the open file dialog box. if (ofdSelectPicture.ShowDialog() == DialogResult.OK) { // Load the picture into the picture box. picShowPicture.Image = Image.FromFile(ofdSelectPicture.FileName); // Show the name of the file in the form's caption. this.Text = string.Concat("Picture Viewer(" + ofdSelectPicture.FileName + ")"); } } catch (System.OutOfMemoryException) { MessageBox.Show("The file you have chosen is not an image file.", "Invalid File", MessageBoxButtons.OK); } What you've just done is wrapped the procedure in an error-handler that watches for and deals with an Out Of Memory Exception. Press F5 to run the project, and follow the steps outlined earlier to load a file that isn't an image. Now, rather than receiving an exception from the IDE, your application displays a custom message box that is much more user friendly, and that won't crash the application to the desktop! Although you have now eliminated the possibility of the user generating an Out Of Memory exception by choosing a file that isn't a valid picture, there are some caveats you should be aware of regarding the code changes you made:
As you can see, the mechanics of adding a TRy structure to handle exceptions is relatively easy, whereas knowing what specifically to catch and how to handle the situation when an exception is caught can prove to be challenging. |