Debugging and Error Handling

>> Aug 19, 2009

So far in this book, you have covered all the basics of simple programming in C#. Before moving on to object - oriented programming in the next part, you need to look at debugging and error handling in C# code.
Errors in code are something that will always be with you. No matter how good a programmer is, problems will always slip through, and part of being a good programmer is realizing this and being prepared to deal with it. Of course, some problems are minor and don ’ t affect the execution of an application, such as a spelling mistake on a button, but glaring errors are also possible, those that cause applications to fail completely (usually known as fatal errors). Fatal errors include simple errors in code that prevent compilation (syntax errors), or more serious problems that occur only at runtime. Some errors are subtle. Perhaps your application fails to add a record to a database because a requested field is missing, or adds a record with the wrong data in other restricted circumstances. Errors such as these, where application logic is in some way flawed, are known as semantic errors, or logic errors.
Often, you won ’ t know about a subtle errors until a user of your application complains that something isn ’ t working properly. This leaves you with the task of tracing through your code to find out what ’ s happening and fixing it so that it does what it was intended to do. In these situations, the debugging capabilities of VS and VCE are a fantastic help. The first part of this chapter looks at some of the techniques available and applies them to some common problems.
Then, you ’ ll learn the error- handling techniques available in C#. These enable you to take precautions in cases where errors are likely, and to write code that is resilient enough to cope with errors that might otherwise be fatal. These techniques are part of the C# language, rather than a debugging feature, but the IDE provides some tools to help you here, too.

This chapter covers two main topics:
. Debugging methods available in Visual Studio .
. Error - handling techniques available in C# .

Debugging in VS and VCE
Earlier, you learned that you can execute applications in two ways: with debugging enabled or without debugging enabled. By default, when you execute an application from VS or VCE, it executes with debugging enabled. This happens, for example, when you press F5 or click the green Play arrow in the toolbar. To execute an application without debugging enabled, choose Debug > Start Without Debugging.
Both VS and VCE allow you to build applications in two configurations: Debug (the default) and Release. (In fact, you can define additional configurations, but that ’ s an advanced technique not covered here.) You can switch between these configurations using the Solution Configurations drop - down in the Standard toolbar.
In VCE this drop - down list is inactive by default. To work through this chapter, enable it by selecting Tools > Options. In the Options dialog, ensure that Show All Settings is selected, choose the General subcategory of the Projects and Solutions category, and enable the Show Advanced Build Configurations option.
When you build an application in debug configuration and execute it in Debug mode, more is going on than the execution of your code. Debug builds maintain symbolic information about your application, so that the IDE knows exactly what is happening as each line of code is executed. Symbolic information means keeping track of, for example, the names of variables used in uncompiled code, so they can be matched to the values in the compiled machine code application, which won ’ t contain such human readable information. This information is contained in . pdbfiles, which you may have seen in your computer’ s Debug directories. This enables you to perform many useful operations:
. Outputting debugging information to the IDE.
. Looking at (and editing) the values of variables in scope during application execution .
. Pausing and restarting program execution .
. Automatically halting execution at certain points in the code.
. Stepping through program execution one line at a time .
. Monitoring changes in variable content during application execution.
. Modifying variable content at runtime .
. Performing test calls of functions.

In the release configuration, application code is optimized, and you cannot perform these operations. However, release builds also run faster, and when you have finished developing an application you will typically supply users with release builds because they won ’ t require the symbolic information that debug builds include.
This section describes debugging techniques you can use to identify and fix areas of code that don ’ t work as expected, a process known as debugging. The techniques are grouped into two sections according to how they are used. In general, debugging is performed either by interrupting program execution or by making notes for later analysis. In VS and VCE terms, an application is either running or is in Break mode — that is, normal execution is halted. You ’ ll look at the Nonbreak mode (runtime or normal) techniques first.

Debugging in Nonbreak (Normal) Mode
One of the commands you ’ ve been using throughout this book is the Console.WriteLine()function, which outputs text to the console. As you are developing applications, this function comes in handy for getting extra feedback on operations:
Console.WriteLine(“MyFunc() Function about to be called.”);
MyFunc(“Do something.”);
Console.WriteLine(“MyFunc() Function execution completed.”);


This code snippet shows how you can get extra information concerning a function called MyFunc() . This is all very well, but it can make your console output a bit cluttered, and when you develop other types of applications, such as Windows forms applications, you won ’ t have a console to output information to. As an alternative, you can output text to a separate location — the Output window in the IDE.
In Chapter 2 , which described the Error List window, it was mentioned that other windows can also be displayed in the same place. One of these, the Output window, can be very useful for debugging. To display this window, select View > Output. This window provides information related to compilation and execution of code, including errors encountered during compilation. You can also use this window, shown in Figure 7 - 1 , to display custom diagnostic information by writing to it directly. Figure 7-1

This window contains a drop - down menu from which different modes can be selected, including Build and Debug. These modes display compilation and runtime information, respectively. When you read “ writing to the Output window ” in this section, it actually means “ writing to the Debug mode view of the Output window. ”
Alternatively, you might want to create a logging file, which has information appended to it when your application is executed. The techniques for doing this are much the same as those for writing text to the Output window, although the process requires an understanding of how to access the file system from C# applications. For now, leave that functionality on the back burner because there is plenty you can do without getting bogged down by file - access techniques.

Outputting Debugging Information
Writing text to the Output window at runtime is easy. You simply replace calls to Console.WriteLine()with the required call to write text where you want it. There are two commands you can use to do this:
. Debug.WriteLine()
. Trace.WriteLine()

These commands function in almost exactly the same way — with one key difference: The first command works in debug builds only; the latter works for release builds as well. In fact, the Debug .WriteLine() command won ’ t even be compiled into a release build; it just disappears, which certainly has its advantages (the compiled code will be smaller, for one thing). You can, in effect, create two versions of your application from a single source file. The debug version displays all kinds of extra diagnostic information, whereas the release version won ’ t have this overhead, and won ’ t display messages to users that might otherwise be annoying!
These functions don ’ t work exactly like Console.WriteLine(). They work with only a single string parameter for the message to output, rather than letting you insert variable values using {X} syntax. This means you must use an alternative technique to embed variable values in strings — for example, the + concatenation. You can also (optionally) supply a second string parameter, which displays a category for the output text. This enables you to see at a glance what output messages are displayed in the Output window, which is useful when similar messages are output from different places in the application.
The general output of these functions is as follows:
:

For example, the following statement, which has “ MyFunc ” as the optional category parameter,
Debug.WriteLine(“Added 1 to i”, “MyFunc”);

would result in the following:
MyFunc: Added 1 to i

The next Try It Out demonstrates outputting debugging information in this way.

Try It Out : Writing Text to the Output Window
1. Create a new console application called Ch07Ex01 and save it in the directory C:\BegVCSharp\Chapter07.
2. Modify the code as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Ch07Ex01
{
class Program
{
static void Main(string[] args)
{
int[] testArray = {4, 7, 4, 2, 7, 3, 7, 8, 3, 9, 1, 9};
int[] maxValIndices;
int maxVal = Maxima(testArray, out maxValIndices);
Console.WriteLine(“Maximum value {0} found at element indices:”,
maxVal);
foreach (int index in maxValIndices)
{
Console.WriteLine(index);
}
Console.ReadKey();
}
static int Maxima(int[] integers, out int[] indices)
{
Debug.WriteLine(“Maximum value search started.”);
indices = new int[1];
int maxVal = integers[0];
indices[0] = 0;
int count = 1;
Debug.WriteLine(string.Format(
“Maximum value initialized to {0}, at element index 0.”, maxVal));
for (int i = 1; i < integers.Length; i++)
{
Debug.WriteLine(string.Format(
“Now looking at element at index {0}.”, i));
if (integers[i] > maxVal)
{
maxVal = integers[i];
count = 1;
indices = new int[1];
indices[0] = i;
Debug.WriteLine(string.Format(
“New maximum found. New value is {0}, at element index {1}.”,
maxVal, i));
}
else
{
if (integers[i] == maxVal)
{
count++;
int[] oldIndices = indices;
indices = new int[count];
oldIndices.CopyTo(indices, 0);
indices[count - 1] = i;
Debug.WriteLine(string.Format(
“Duplicate maximum found at element index {0}.”, i));
}
}
}
Trace.WriteLine(string.Format(
“Maximum value {0} found, with {1} occurrences.”, maxVal, count));
Debug.WriteLine(“Maximum value search completed.”);
return maxVal;
}
}
}


3. Execute the code in Debug mode. The result is shown in Figure 7 - 2. Figure 7-2

4. Terminate the application and check the contents of the Output window (in Debug mode). A truncated version of the output is shown here:
...

Maximum value search started.
Maximum value initialized to 4, at element index 0.
Now looking at element at index 1.
New maximum found. New value is 7, at element index 1.
Now looking at element at index 2.
Now looking at element at index 3.
Now looking at element at index 4.
Duplicate maximum found at element index 4.
Now looking at element at index 5.
Now looking at element at index 6.
Duplicate maximum found at element index 6.
Now looking at element at index 7.
New maximum found. New value is 8, at element index 7.
Now looking at element at index 8.
Now looking at element at index 9.
New maximum found. New value is 9, at element index 9.
Now looking at element at index 10.
Now looking at element at index 11.
Duplicate maximum found at element index 11.
Maximum value 9 found, with 2 occurrences.
Maximum value search completed.
The thread ‘ < No Name > ’ (0xcc8) has exited with code 0 (0x0).
The thread ‘ < No Name > ’ (0xccc) has exited with code 0 (0x0).
The program ‘[3648] Ch07Ex01.vshost.exe: Managed’ has exited with code 0 (0x0).


5. Change to Release mode using the drop - down menu on the Standard toolbar, as shown in Figure 7 - 3. Figure 7-3
6. Run the program again, this time in Release mode, and recheck the Output window when execution terminates. The output (again truncated) is as follows:
...
Maximum value 9 found, with 2 occurrences.
The thread ‘ < No Name > ’ (0xe2c) has exited with code 0 (0x0).
The thread ‘ < No Name > ’ (0xd2c) has exited with code 0 (0x0).
The program ‘[1736] Ch07Ex01.vshost.exe: Managed’ has exited with code 0 (0x0).


How It Works
This application is an expanded version of one shown in Chapter 6 , using a function to calculate the maximum value in an integer array. This version also returns an array of the indices where maximum values are found in an array, so that the calling code can manipulate these elements.
First, an additional using directive appears at the beginning of the code:
using System.Diagnostics;

This simplifies access to the functions discussed earlier because they are contained in the System.Diagnostics namespace. Without this using directive, code such as
Debug.WriteLine(“Bananas”);

would need further qualification, and would have to be rewritten as
System.Diagnostics.Debug.WriteLine(“Bananas”);

The using directive keeps your code simple and reduces verbosity.
The code in Main()simply initializes a test array of integers called testArray; it also declares another integer array called maxValIndices to store the index output of Maxima()(the function that performs the calculation), and then calls this function. Once the function returns, the code simply outputs the results.
Maxima()is slightly more complicated, but it doesn ’ t use much code that you haven ’ t already seen. The search through the array is performed in a similar way to the MaxVal()function Chapter 6 , but a record is kept of the indices of maximum values.
Especially note (other than the lines that output debugging information) the function used to keep track of the indices. Rather than return an array that would be large enough to store every index in the source array (needing the same dimensions as the source array), Maxima()returns an array just large enough to hold the indices found. It does this by continually recreating arrays of different sizes as the search progresses. This is necessary because arrays can ’ t be resized once they are created.
The search is initialized by assuming that the first element in the source array (called integers locally) is the maximum value and that there is only one maximum value in the array. Values can therefore be set for maxVal (the return value of the function and the maximum value found) and indices, the out parameter array that stores the indices of the maximum values found. maxValis assigned the value of the first element in integers, and indicesis assigned a single value, simply 0, which is the index of the array ’ s first element. You also store the number of maximum values found in a variable called count, which enables you to keep track of the indicesarray.
The main body of the function is a loop that cycles through the values in the integers array, omitting the first one because it has already been processed. Each value is compared to the current value of maxVal and ignored if maxVal is greater. If the currently inspected array value is greater than maxVal, then maxValand indices are changed to reflect this. If the value is equal to maxVal, then countis incremented and a new array is substituted for indices. This new array is one element bigger than the old indices array, containing the new index found.
The code for this last piece of functionality is as follows:
if (integers[i] == maxVal)
{
count++;
int[] oldIndices = indices;
indices = new int[count];
oldIndices.CopyTo(indices, 0);
indices[count - 1] = i;
Debug.WriteLine(string.Format(
“Duplicate maximum found at element index {0}.”, i));
}


This works by backing up the old indicesarray into oldIndices, an integer array local to this if code block. Note that the values in oldIndices are copied into the new indicesarray using the < array > .CopyTo()function. This function simply takes a target array and an index to use for the first element to copy to and pastes all values into the target array.
Throughout the code, various pieces of text are output using the Debug.WriteLine()and Trace.WriteLine()functions. These functions use the string.Format()function to embed variable values in strings in the same way as Console.WriteLine(). This is slightly more efficient than using the + concatenation operator.
When you run the application in Debug mode, you see a complete record of the steps taken in the loop that give you the result. In Release mode, you see just the result of the calculation, because no calls to Debug.WriteLine()are made in release builds.
In addition to these WriteLine()functions are a few more you should be aware of. To start with, there are equivalents to Console.Write():
. Debug.Write()
. Trace.Write()

Both functions use the same syntax as the WriteLine()functions (one or two parameters, with a message and an optional category), but differ in that they don ’ t add end - of - line characters.
There are also the following commands:
. Debug.WriteLineIf()
. Trace.WriteLineIf()
. Debug.WriteIf()
. Trace.WriteIf()

Each of these has the same parameters as the non - Ifcounterparts, with the addition of an extra, mandatory parameter that precedes them in the parameter list. This parameter takes a Booleanvalue (or an expression that evaluates to a Boolean value) and results in the function only writing text if this value evaluates to true. You can use these functions to conditionally output text to the Output window.
For example, you might require debugging information to be output in only certain situations, so you can have a great many Debug.WriteLineIf()statements in your code that all depend on a certain condition being met. If this condition doesn ’ t occur, then they aren ’ t displayed, which prevents the Output window from being cluttered with superfluous information.

Tracepoints
Tracepoints are a feature only available in VS, not in VCE. If you are using VCE, you may choose to skip this section.
An alternative to writing information to the Output window is to use tracepoints. These are a feature of VS, rather than C#, but they serve the same function as using Debug.WriteLine(). Essentially, they enable you to output debugging information without modifying your code.
To demonstrate tracepoints, you can use them to replace the debugging commands in the previous example. (See the Ch07Ex01TracePoints file in the downloadable code for this chapter.) The process for adding a tracepoint is as follows:
1. Position the cursor at the line where you want the tracepoint to be inserted. The tracepoint will be processed before this line of code is executed.
2. Right - click the line of code and select Breakpoint > Insert Tracepoint.
3. Type the string to be output in the Print a Message text box in the When Breakpoint Is Hit dialog that appears. If you want to output variable values, enclose the variable name in curly braces.
4. Click OK.
5. A red diamond appears to the left of the line of code containing a tracepoint, and the line of code itself is shown with red highlighting.

As implied by the title of the dialog for adding tracepoints, and the menu selections required for them, tracepoints are a form of breakpoints (and can cause application execution to pause, just like a breakpoint, if desired). You look at breakpoints, which typically serve a more advanced debugging purpose, a little later in the chapter.
Figure 7 - 4 shows the breakpoint required for line 31 of Ch07Ex01TracePoints, where line numbering applies to the code after the existing Debug.WriteLine()statements have been removed. Figure 7-4

As shown in the text in Figure 7 - 4 , tracepoints allow you to insert other useful information concerning the location and context of the tracepoint. You should experiment with these values, particularly $FUNCTIONand $CALLER, to see what additional information you can glean. You can also see that it is possible for the tracepoint to execute a macro, an advanced feature that isn ’t covered here.
There is another window you can use to quickly see the tracepoints in an application. To display this window, select Debug > Windows > Breakpoints from the VS menu. This is a general window for displaying breakpoints (tracepoints, as noted earlier, are a form of breakpoint). You can customize the display to show more tracepoint - specific information by adding the When Hit column from the Columns drop - down in this window. Figure 7 - 5 shows the display with this column configured and all the tracepoints added to Ch07Ex01TracePoints. Figure 7-5

Executing this application in Debug mode has the same result as before. You can remove or temporarily disable tracepoints by right - clicking on them in the code window or via the Breakpoints window. In the Breakpoints window, the check box to the left of the tracepoint shows if the tracepoint is enabled; disabled tracepoints are unchecked and displayed in the code window as diamond outlines, rather than solid diamonds.

Diagnostics Output Versus Tracepoints
Now that you have seen two methods of outputting essentially the same information, consider the pros and cons of each. First, tracepoints have no equivalent to the Trace commands; that is, there is no way to output information in a release build using tracepoints. This is because tracepoints are not included in your application. Tracepoints are handled by Visual Studio, and as such do not exist in the compiled version of your application. You will see tracepoints doing something only when your application is running in the VS debugger.
The chief disadvantage of tracepoints is also their major advantage, which is that they are stored in VS. This makes them quick and easy to add to your applications as and when you need them, but also all too easy to delete. Deleting a tracepoint is as simple as clicking on the red diamond indicating its position, which can be annoying if you are outputting a complicated string of information.
One bonus of tracepoints, though, is the additional information that can be easily added, such as $FUNCTION, as noted in the previous section. While this information is available to code written using Debug and Trace commands, it is trickier to obtain. In summary, use these two methods of outputting debug information as follows:
. Diagnostics output: Use when debug output is something you always want to output from an application, particularly where the string you want to output is complex, involving several variables or a lot of information. In addition, Trace commands are often the only option should you want output during execution of an application built in Release mode.
. Tracepoints: Use these when debugging an application to quickly output important information that may help you resolve semantic errors.

There is also the obvious difference that tracepoints are only available in VS, whereas diagnostics output is available in both VS and VCE.

Debugging in Break Mode
The rest of the debugging techniques described in this chapter work in Break mode. This mode can be entered in several ways, all of which result in the program pausing in some way.

Entering Break Mode
The simplest way to enter Break mode is to click the pause button in the IDE while an application is running. This pause button is found on the Debug toolbar, which you should add to the toolbars that appear by default in VS. To do this, right - click in the toolbar area and select Debug, as shown in Figure 7 - 6. Figure 7-6

Figure 7 - 7 shows the Debug toolbar that appears. Figure 7-7

The first four buttons on the toolbar allow manual control of breaking. In Figure 7 - 7 , three of these are grayed out, because they won ’ t work with a program that isn ’ t currently executing. The one that is enabled, Start, is identical to the button that exists on the standard toolbar. The following sections describe the rest of the buttons as needed.
When an application is running, the toolbar changes to look like Figure 7 - 8. Figure 7-8

The three buttons next to Start that were grayed out now enable you to do the following:
. Pause the application and enter Break mode.
. Stop the application completely (this doesn ’ t enter Break mode, it just quits).
. Restart the application.

Pausing the application is perhaps the simplest way to enter Break mode, but it doesn ’ t give you fine - grained control over exactly where to stop. You are likely to stop in a natural pause in the application, perhaps where you request user input. You might also be able to enter Break mode during a lengthy operation, or a long loop, but the exact stop point is likely to be fairly random. In general, it is far better to use breakpoints.

Breakpoints
A breakpoint is a marker in your source code that triggers automatic entry into Break mode. Breakpoints are available in both VS and VCE, but they are more flexible in VS. Breakpoints may be configured to do the following:
. Enter Break mode immediately when the breakpoint is reached.
. (VS only.) Enter Break mode when the breakpoint is reached if a Boolean expression evaluates to true.
. (VS only.) Enter Break mode once the breakpoint is reached a set number of times.
. (VS only.) Enter Break mode once the breakpoint is reached and a variable value has changed since the last time the breakpoint was reached.
. (VS only.) Output text to the Output window or execute a macro (see the section “ Tracepoints ” earlier in the chapter).

These features are available only in debug builds. If you compile a release build, all breakpoints are ignored.
There are several ways to add breakpoints. To add simple breakpoints that break when a line is reached, simply left - click on the gray area to the left of the line of code, right - click on the line, and select Breakpoint > Insert Breakpoint; select Debug > Toggle Breakpoint from the menu; or press F9. Figure 7 - 9 shows the right - clicking on a line option for VS (in VCE the menu is slightly different, and the Insert Tracepoint menu item does not appear). Figure 7-9

The breakpoint appears as a red circle next to the line of code, which is highlighted, as shown in Figure 7 - 10. Figure 7-10

The remainder of this section applies only to VS and not VCE. If you are using VCE, then feel free to skip ahead to the section “ Other Ways to Enter Break Mode. ”
In VS you can also see information about a file ’ s breakpoints using the Breakpoints window (you saw how to enable this window earlier). You can use the Breakpoints window to disable breakpoints (by removing the tick to the left of a description; a disabled breakpoint shows up as an unfilled red circle), to delete breakpoints, and to edit the properties of breakpoints.
The columns shown in this window, Condition and Hit Count, are only two of the available ones, but they are the most useful. You can edit these by right - clicking a breakpoint (in code or in this window) and selecting Condition or Hit Count.

Selecting Condition opens the dialog shown in Figure 7 - 11. Figure 7-11

Here, you can type any Boolean expression, which may involve any variables in scope at the breakpoint. Figure 7 - 11 shows a breakpoint that triggers when it is reached and the value of maxVal is greater than 4. You can also check whether this expression has changed and only trigger the breakpoint then (you might trigger it if maxVal changed from 2 to 6 between breakpoint encounters, for example).
Selecting Hit Count opens the dialog shown in Figure 7 - 12. Figure 7-12

Here you can specify how many times a breakpoint needs to be hit before it is triggered. The drop - down list offers the following options:
. Break always
. Break when the hit count is equal to
. Break when the hit count is a multiple of
. Break when the hit count is greater than or equal to

The option chosen, combined with the value entered in the text box next to the list, determines the behavior of the breakpoint. The hit count is useful in long loops, when you might want to break after, say, the first 5000 cycles. It would be a pain to break and restart 5000 times if you couldn ’ t do this!
A breakpoint with additional properties set (such as a condition or hit count) is displayed slightly differently. Instead of a simple red circle, a configured breakpoint consists of a red circle containing a white + (plus) symbol. This can be useful because it enables you to see at a glance which breakpoints will always cause Break mode to be entered and which will only do so in certain circumstances.

Other Ways to Enter Break Mode
There are two more ways to get into Break mode. One is to enter it when an unhandled exception is thrown. This subject is covered later in this chapter, when you look at error handling. The other way is to break when an assertion is generated.
Assertions are instructions that can interrupt application execution with a user- defined message. They are often used during application development to test whether things are going smoothly. For example, at some point in your application you might require a given variable to have a value less than 10. You can use an assertion to confirm that this is true and interrupt the program if it isn ’ t. When the assertion occurs, you have the option to Abort, which terminates the application; Retry, which causes Break mode to be entered; or Ignore, which causes the application to continue as normal.
As with the debug output functions shown earlier, there are two versions of the assertion function:
. Debug.Assert()
. Trace.Assert()

Again, the debug version is only compiled into debug builds.
These functions take three parameters. The first is a Boolean value, whereby a value of falsecauses the assertion to trigger. The second and third are string parameters to write information both to a pop - up dialog and the Output window. The preceding example would need a function call such as the following:
Debug.Assert(myVar < 10, “myVar is 10 or greater.”,
“Assertion occurred in Main().”);


Assertions are often useful in the early stages of user adoption of an application. You can distribute release builds of your application containing Trace.Assert()functions to keep tabs on things. Should an assertion be triggered, the user will be informed, and this information can be passed on to you. You can then determine what has gone wrong even if you don ’ t know how it went wrong.
You might, for example, provide a brief description of the error in the first string, with instructions as to what to do next as the second string:
Trace.Assert(myVar < 10, “Variable out of bounds.”,
“Please contact vendor with the error code KCW001.”);


Should this assertion occur, the user will see the dialog shown in Figure 7 - 13. Figure 7-13
Admittedly, this isn ’ t the most user- friendly dialog in the world, as it contains a lot of information that could confuse users, but if they send you a screenshot of the error, you could quickly track down the problem.
Now it ’ s time to look at what you can actually do after application execution is halted and you are in Break mode. In general, you enter Break mode to find an error in your code (or to reassure yourself that things are working properly). Once you are in Break mode, you can use various techniques, all of which enable you to analyze your code and the exact state of the application at the point in its execution where it is paused.

Monitoring Variable Content
Monitoring variable content is just one example of how VS and VCE help you a great deal by making things simple. The easiest way to check the value of a variable is to hover the mouse over its name in the source code while in Break mode. A yellow tooltip showing information about the variable appears, including the variable ’ s current value.
You can also highlight entire expressions to get information about their results in the same way. For more complex values, such as arrays, you can even expand values in the tooltip to see individual element entries.
You may have noticed that when you run an application, the layout of the various windows in the IDE changes. By default, the following changes are likely to occur at runtime (this behavior may vary slightly depending on your installation):
. The Properties window disappears, along with some other windows, probably including the Solution Explorer window.
. The Error List window is replaced with two new windows across the bottom of the IDE window.
. Several new tabs appear in the new windows.

The new screen layout is shown in Figure 7 - 14 . This may not match your display exactly, and some of the tabs and windows may not look exactly the same, but the functionality of these windows as described later will be the same, and this display is completely customizable via the View and Debug > Windows menus (during Break mode), as well as by dragging windows around the screen to reposition them. Figure 7-14

The new window that appears in the bottom left corner is particularly useful for debugging. It enables you to keep tabs on the values of variables in your application when in Break mode. The tabs displayed here vary between VS and VCE:
. Autos (VS only): Variables in use in the current and previous statements (Ctrl+D, A)
. Locals: All variables in scope (Ctrl+D, L)
. Watch N: Customizable variable and expression display (where N is 1 to 4, found on Debug > Windows > Watch)

All these tabs work in more or less the same way, with various additional features depending on their specific function. In general, each tab contains a list of variables, with information on each variable ’ s name, value, and type. More complex variables, such as arrays, may be further examined using the + and – tree expansion/contraction symbols to the left of their names, allowing a tree view of their content. For example, Figure 7 - 15 shows the Locals tab obtained by placing a breakpoint in the example code. It shows the expanded view for one of the array variables, maxValIndices. Figure 7-15

You can also edit the content of variables from this view. This effectively bypasses any other variable assignment that might have happened in earlier code. To do this, simply type a new value into the Value column for the variable you want to edit. You might do this to try out some scenarios that would otherwise require code changes, for example.
The Watch window (or Watch windows in VS, which can display up to four) enables you to monitor specific variables or expressions involving specific variables. To use this window, simply type the name of a variable or expression into the Name column and view the results. Note that not all variables in an application are in scope all the time and are labeled as such in a Watch window. For example, Figure 7 - 16 shows a Watch window with a few sample variables and expressions in it. Figure 7-16

The testArrayarray is local to Main(), so you don ’ t see a value here. Instead, you get a message informing you that the variable isn ’ t in scope.
You can also add variables to a Watch window by dragging them from the source code into the window.
One nice feature about the various displays of variables accessible in this window is that they show you variables that have changed between breakpoints. Any new value is shown in red, rather than black, making it easy to see whether a value has changed.
As mentioned earlier, to add more Watch windows in VS, in Break mode you can use the Debug > Windows > Watch > Watch N menu options to toggle the four possible windows on or off. Each window may contain an individual set of watches on variables and expressions, so you can group related variables together for easy access.
As well as these windows, VS also has a QuickWatch window that provides detailed information about a variable in the source code. To use this, simply right - click the variable you want to examine and select the QuickWatch menu option. In most cases, though, it is just as easy to use the standard Watch windows.
Note that watches are maintained between application executions. If you terminate an application and then rerun it, you don ’ t have to add watches again — the IDE remembers what you were looking at the last time.

Stepping through Code
So far, you ’ ve learned how to discover what is going on in your applications at the point where Break mode is entered. Now it ’ s time to see how you can use the IDE to step through code while remaining in Break mode, which enables you to see the exact results of the code being executed. This is an extremely valuable technique for those of us who can ’ t think as fast as computers can.
When Break mode is entered, a cursor appears to the left of the code view (which may initially appear inside the red circle of a breakpoint if a breakpoint was used to enter Break mode), by the line of code that is about to be executed, as shown in Figure 7 - 17. Figure 7-17

This shows you what point execution has reached when Break mode is entered. At this point, you can have execution proceed on a line - by - line basis. To do so, you use some of the Debug toolbar buttons shown in Figure 7 - 18. Figure 7-18

The sixth, seventh, and eighth icons control program flow in Break mode. In order, they are as follows:
. Step Into: Execute and move to the next statement to execute.
. Step Over: This is similar to Step Into, but won ’ t enter nested blocks of code, including functions.
. Step Out: This will run to the end of the code block and resume Break mode at the statement that follows.

To look at every single operation carried out by the application, you can use Step Into to follow the instructions sequentially. This includes moving inside functions, such as Maxima()in the preceding example. Clicking this icon when the cursor reaches line 15, the call to Maxima(), results in the cursor moving to the first line inside the Maxima()function. Alternatively, clicking Step Over when you reach line 15 moves the cursor straight to line 16, without going through the code in Maxima()(although this code is still executed). If you do step into a function that you aren ’ t interested in, you can click Step Out to return to the code that called the function. As you step through code, the values of variables are likely to change. If you keep an eye on the monitoring windows just discussed, you can clearly see this happening.
In code that has semantic errors, this technique may be the most useful one at your disposal. You can step through code right up to the point where you expect problems to occur, and the errors will be generated as if you were running the program normally. Along the way, watch the data to see just what is going wrong. Later in this chapter, you use this technique to find out what is happening in an example application.

Immediate and Command Windows
The Command (VS only) and Immediate windows (found on the Debug > Windows menu) enable you to execute commands while an application is running. The Command window enables you to perform VS operations manually (such as menu and toolbar operations), and the Immediate window enables you to execute additional code besides the source code lines being executed and to evaluate expressions. In VS, these windows are intrinsically linked (in fact, earlier versions of VS treated them as the same thing). You can even switch between them by entering commands: immed to move from the Command window to the Immediate window and > cmdto move back.
This section concentrates on the Immediate window because the Command window is only really useful for complex operations and is only available in VS, whereas the Immediate window is available in both VS and VCE. The simplest use of this window is to evaluate expressions, a bit like a one - shot use of the Watch windows. To do this, type an expression and press Return. The information requested will then be displayed. An example is shown in Figure 7 - 19. Figure 7-19

You can also change variable content here, as demonstrated in Figure 7 - 20. Figure 7-20

In most cases, you can get the effects you want more easily using the variable monitoring windows shown earlier, but this technique is still handy for tweaking values, and it ’ s good for testing expressions for which you are unlikely to be interested in the results later.

The Call Stack Window
The final window to look at shows you the way in which the current location was reached. In simple terms, this means showing the current function along with the function that called it, the function that called that, and so on (that is, a list of nested function calls). The exact points where calls are made are also recorded.
In the earlier example, entering Break mode when in Maxima(), or moving into this function using code stepping, reveals the information shown in Figure 7 - 21. Figure 7-21

If you double - click an entry, you are taken to the appropriate location, enabling you to track the way code execution has reached the current point. This window is particularly useful when errors are first detected, because you can see what happened immediately before the error. Where errors occur in commonly used functions, this helps you determine the source of the error.
Sometimes this window shows some very confusing information. For example, errors may occur outside of your applications due to using external functions in the wrong way. In such cases, this window could contain a long list of entries, but only one or two might look familiar. You can see external references (should you ever need to) by right - clicking in the window and selecting Show External Code.

Error Handling
The first part of this chapter explained how to find and correct errors during application development so that they won ’ t occur in release - level code. Sometimes, however, you know that errors are likely to occur and there is no way to be 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 term for all techniques of this nature, and this section looks at exceptions and how you can deal with them. 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. ” Strictly speaking, this isn ’ t an error outside of the context of the function, although it is treated as one by the code that calls the function.
You ’ ve seen exceptions a few times already in this book. Perhaps the simplest example is attempting to address an array element that is out of range:
int[] myArray = {1, 2, 3, 4};
int myElem = myArray[4];


This outputs the following exception message and then terminates the application:
Index was outside the bounds of the array.

You ’ ve already seen some examples of the window that is displayed. It has a line connecting it to the offending code and includes links to reference topics in the .NET help files, as well as a View Detail link to more information about the exception.
Exceptions are defined in namespaces, and most have names that make their purpose clear. 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 appears, and the application terminates, only when the exception is unhandled. In the next section, you ’ ll see exactly what you have to do to handle an exception.

try . . . catch . . . finally
The C# language includes syntax for structured exception handling (SEH). Three keywords mark code as being able to handle exceptions, along with instructions as to what to do when an exception occurs: 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 tryblock and a finallyblock with no catchblock, or a try block with multiple catch blocks. If one or more catchblocks exist, then the finallyblock is optional; otherwise, 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. catchblocks may be set to respond only to specific exception types (such as System.IndexOutOfRangeException ) using < exceptionType > , hence the ability to provide multiple catchblocks. It is also possible to omit this parameter entirely, to get a general catch block that responds to all exceptions.
. finally: Contains code that is always executed, either after the tryblock 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).

Here ’ s the sequence of events that occurs after an exception occurs in code in a tryblock:
. The try block terminates at the point where the exception occurred.
. If a catchblock exists, then a check is made to determine whether the block matches the type of exception that was thrown. If no catchblock exists, then the finallyblock (which must be present if there are no catchblocks) executes.
. If a catch block exists but there is no match, then a check is made for other catchblocks.
. If a catchblock matches the exception type, then the code it contains executes, and then the finally block executes if it is present.
. If no catchblocks match the exception type, then the finallyblock of code executes if it is present.

The following Try It Out demonstrates handling exceptions, throwing and handling them in several ways so you can see how things work.

Try It Out : Exception Handling
1. Create a new console application called Ch07Ex02 and save it in the directory C:\BegVCSharp\Chapter07.
2. Modify the code as follows (the line number comments shown here will help you match up your code to the discussion afterward, 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. Figure 7-22

How It Works
This application has a tryblock 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.IndexOutOfRangeExceptionexception
. 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 shown earlier in the chapter. By working your way through the code one 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 as follows:
Console.WriteLine(“Main() try block reached.”);

Here, code is referred to 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 (Tools > Options or the Text Editor > C# General option section). Comments are included in the preceding code 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 confirm 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. eTypeand args are no longer in scope (they are local to Main()); instead, you see the local exceptionTypeargument, which is of course “ none ” . Keep pressing Step Into and you ’ ll reach the switchstatement that checks the value of exceptionTypeand executes the code that writes out the string Not throwing an exception to the screen. When you execute the breakstatement (on line 54), you exit the function and resume processing in Main()at line 26. Because no exception was thrown, the tryblock continues.
Next, processing continues with the finally block. Click Step Into a few more times to complete the finally blockand 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 Systemnamespace, System.Exception.
No break;statement is necessary in this case: block–throwis enough to end execution of the block.

When you process this statement with Step Into, you find yourself at the general catchblock 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 finallyblock, and back into another loop cycle that calls ThrowException()with a new parameter on line 25. This time the parameter is “ index ” .
Now 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.IndexOutOfRangeExceptionexception.
This time there is a matched catchblock 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 finallyblock (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 tryblock, starting on line 63. When you reach line 68, you perform a nested call to ThrowException() , this time with the parameter “ index ” . You can 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, but this time it ’ s 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 catchblock (starting on line 70) deals with it.
As with the earlier exception handling, you now step through this catchblock and the associated finally block, and reach the end of the function call, but with one crucial difference: Although an exception was thrown, it was also handled — by the code in ThrowException(). This means there is no exception left to handle in Main(), so you go straight to the finallyblock, and then the
application terminates.

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. The IDE supplies a dialog for examining and editing the available exceptions, which can be called up with the Debug > Exceptions menu item (or by pressing Ctrl+D, E). This dialog is shown in Figure 7 - 23 (the entries in the list may vary if you use VS). 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 earlier.
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 enables you to ignore unhandled exceptions, and suffer the consequences. In most cases, this results in Break mode being entered, so you will likely need to do this only in exceptional circumstances.
Typically, the default settings here are fine.

Notes on Exception Handling
You must always supply catch blocks for more specific exceptions before more general catching. If you get this wrong, the application will fail to compile. Note also that you can throw exceptions from within catch blocks, either in the ways used in the previous example or simply by using the following 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...finallyblock, but by parent code (although the finally block in the nested structure will still execute).
For example, if you changed the try...catch...finallyblock 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. Figure 7-24

This screenshot shows extra lines of output from the Main()function, as the System.IndexOutOfRangeExceptionis caught in this function.

Summary
This chapter has concentrated on techniques that you can use to debug your applications. A variety of techniques are possible here, most of which are available for whatever type of project you are creating, not just console applications.
In this chapter, you learned to do the following:
. Use Debug.WriteLine()and Trace.WriteLine()to write text to the Output window.
. Use tracepoints to write text to the Output window.
. Enter and use Break mode, including the versatile breakpoints .
. Use debugging information windows in VS.
. Step through code .
. Handle exceptions using try ... catch... finally.

You have now covered everything that you need to produce simple console applications, along with the methods for debugging them. In the next part of this book, you ’ ll look at the powerful technique of object - oriented programming.

Exercises
1. “ Using Trace.WriteLine()is preferable to using Debug.WriteLine(), as the Debugversion only works in debug builds. ” Do you agree with this statement? If so, why?
2. Provide code for a simple application containing a loop that generates an error after 5000 cycles. Use a breakpoint to enter Break mode just before the error is caused on the 5000th cycle.
(Note: A simple way to generate an error is to attempt to access a nonexistent array element, such as myArray[1000] in an array with a hundred elements.)
3. “finallycode blocks only execute if a catch block isn ’ t executed. ” True or false?
4. Given the enumeration data type orientationdefined in the following code, write an application that uses structured exception handling (SEH) to cast a bytetype variable into an orientation type variable in a safe way. (Note: You can force exceptions to be thrown using the checked keyword, an example of which is shown here. This code should be used in your application.)
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
myDirection = checked((orientation)myByte);

Post a Comment

About This Blog

  © Blogger template Simple n' Sweet by Ourblogtemplates.com 2009

Back to TOP