C# code Debugging tips

Jatin Mohan

The article is targeted at developers starting off with C#. If you are an experienced hand then perhaps you may want to skip reading this article.

No one writes perfect code. You're most certainly familiar with those problems that prevent code from executing properly—they're called bugs. Being new to C#, your code will probably contain a fair number of bugs. As you gain proficiency, the number of bugs in your code will decrease, but they will never disappear entirely. Debugging is a skill and an art. This article can't teach you how to debug every possible build or runtime error you may encounter; however, in this article you will learn the basic skills necessary to trace and correct most bugs in your code.

Writing some code
Before proceeding, create a new Windows Application project named Debugging Example. Change the name of the default form to fclsDebuggingExample, set its Text property to Debugging Example, and change the Main() entry point of the project to reference fclsDebuggingExample instead of Form1.

Add a new text box to the form by double-clicking the TextBox item in the toolbox. Set the text box's properties as follows:

Property

Value

Name

txtInput

Location

88,112

Size

120,20

Text

(make blank)

Next, add a new button to the form by double-clicking the Button item in the toolbox, and then set its properties as follows:

Property

Value

Name

btnPerformDivision

Location

96,144

Size

104,23

Text

Perform Division

Your form should now look like the one shown in figure 1.
All this little project will do is divide 100 by whatever is entered into the text box. As you write the code to accomplish this, various bugs will be introduced (on purpose), and you'll learn to correct them. Save your project now by clicking the Save All button on the toolbar.

Adding Comments to Your Code

One of the simplest things you can do to reduce bugs from the start—and make tracking down existing bugs easier—is to add comments to your code. A code comment is simply a line of text that C# knows isn't actual code. Comment lines are stripped from the code when the project is compiled to create a distributable component, so comments don't affect performance. C#'s code window shows comments as green text. This makes it easier to read and understand procedures. You should consider adding comments to the top of each procedure stating the purpose of the procedure. In addition, you should add liberal comments throughout all procedures, detailing what's occurring in the code.

Comments are meant to be read by humans, not by computers. Strive to make your comments intelligible. Keep in mind that a comment that is hard to understand is not much better than no comment at all. Also, remember that comments serve as a form of documentation. Just as documentation for an application must be clearly written, code comments should also follow good writing principles

To create a comment, precede the comment text with two forward slash marks (//). For example, a simple comment might look like this:

// This is a comment because it is preceded with double forward slashes.

Comments can also be placed at the end of a line of code, like this:

int intAge;        // Used to store the user's age in years.

Everything to the right of and including the double forward slashes in this statement is a comment. C# also supports a second type of comment. This allows for comments to span multiple lines without forcing the developer to add // characters to each line. The comment begins with an open comment mark of a forward slash, followed by an asterisk (/*), and the comment closes with a close mark of an asterisk followed by a forward slash (*/). For example, a comment can look like this:

/*     
focuses on debugging code, a topic
every developer spends a lot of time on.  */

By adding comments to your code, you don't have to rely on memory to decipher the code's purpose or mechanics. If you've ever had to go back and work with code you haven't looked at in a while, or had to work with someone else's code, you probably already have a great appreciation for comments.
Double-click the button now to access its Click event and add the following two lines of code (comments, actually):

// This procedure divides 100 by the value entered in
// the text box txtInput.

Notice that after you enter the second forward slash, both slashes turn green. Comments, whether single line (//) or multiline (/* comments */), will be displayed in a green font in Visual Studio.
When creating code comments, strive to do the following:

  • Document the purpose of the code (the why, not the how).
  • Clearly indicate the thinking and logic behind the code.
  • Call attention to important turning points in code.
  • Reduce the need for readers to run a simulation of code execution in their heads.

C# also supports an additional type of comment denoted with three slashes (///). When the C# compiler encounters these comments, it processes them into an XML file. These types of comments are often used to create documentation for code. Creating XML files from comments is beyond the scope of this article, but if these features intrigue you, I highly recommend that you look into it.

Using C#'s Debugging Tools

C# includes a number of debugging tools to help you track down and eliminate bugs. In this section, you'll learn how to use break points, the Command window, and the Output window—three tools that form the foundation of any debugging arsenal.
The same way an exception halts the execution of a method, you can deliberately stop execution at any statement of code by creating a break point. When C# encounters a break point while executing code, execution is halted at the break statement, prior to it being executed. Break points enable you to query or change the value of variables at a specific instance in time, and they let you step through code execution one line at a time

You're going to create a break point to help troubleshoot the exception in your MessageBox.Show() statement.
Adding a break point is simple. Just click in the gray area to the left of the statement at which you want to break code execution. When you do so, C# displays a red circle, denoting a break point at that statement (see Figure 2). To clear a break point, click the red circle.

Break points give you control over code execution.

Break points are saved with the project. This makes it much easier to suspend a debugging session; you don't have to reset all your break points each time you open the project.

 

Set a new break point on the statement. (the statement where lngAnswer is set). Do this by clicking in the gray area to the left of the statement. After you've set the break point, press F5 to run the program. Again, click the button. When C# encounters the break point, code execution is halted and the procedure with the break point is shown. In addition, the cursor is conveniently placed at the statement with the current break point. Notice the yellow arrow overlaying the red circle of the break point  (VS 2005). This yellow arrow marks the next statement to be executed. It just so happens that the statement has a break point, so the yellow arrow appears over the red circle (the yellow arrow won't always be over a red circle, but it will always appear in the gray area aligned with the next statement to execute).

 

When code execution is halted at a break point, you can do a number of things. See Table1 for a list of the most common actions. For now, press F5 to continue program execution. Again, you get the Format exception. Click Break to access the code procedure with the error.

Table.1. Actions That Can Be Taken at a Break Point

Action

Keystroke

Description

Continue Code Execution

F5

Continues execution at the current break statement.

Step Into

F11

Executes the statement at the break point and then stops at the next statement. If the current statement is a function call, F11 enters the function and stops at the first statement in the function.

Step Over

F10

Executes the statement at the break point and then stops at the next statement. If the current statement is a function call, the function is run in its entirety; then execution stops at the statement following the function call.

Step Out

Shift+F11

Runs all the statements in the current procedure and halts execution at the statement following the one that called the current procedure.

Using the Command Window

Break points themselves aren't usually sufficient to debug a procedure. In addition to break points, you'll often use the Command window to debug code. The Command window is a Visual Studio IDE window that generally appears only when your project is in Run mode. If the Command window isn't displayed, press Ctrl+Alt+A to display it now (or use the Other Views submenu of the View menu). Using the Command window, you can type in code statements that C# executes immediately. You'll use the Command window now to debug our problem statement example.
Type the following statement into the Command window and press Enter:

? txtInput.Text

Although not intuitive, the ? character has been used in programming for many years as a shortcut for the word "print." The statement that you entered simply prints the contents of the Text property of the text box.
Notice how the command window displays "" on the line below the statement you entered. This indicates that the text box contains an empty string (also called a zero-length string). The statement throwing the exception is attempting to use the long.Parse() method to convert the contents of the text box to a Long. The long.Parse() method expects data to be passed to it, yet the text box has no data (the Text property is empty). Consequently, a Format exception occurs.

Generally, when you receive a Format exception, you should look at any variables or properties being referenced to ensure that the data they contain is appropriate data for the statement. Often, you'll find that the code is trying to perform an operation that is inappropriate for the data being supplied.

You can do a number of things to prevent this error. The most obvious is to ensure that the text box contains a value before attempting to use the long.Parse() method. You'll do this now. C# doesn't allow you to modify code when in break mode, so choose Stop Debugging from the Debug menu before continuing.

Add the following statement to your method, right above the statement that throws the exception (the one with the break point):

if (txtInput.Text == "") return;

Press F5 to run the project once more, and then click the button. This time, C# won't throw an exception, and it won't halt execution at your break point; the test you just created causes code execution to leave the procedure before the statement with the break point is reached.
Type your name into the text box and click the button again. Now that the text box is no longer empty, execution passes the statement with the exit test and stops at the break point. Press F5 to continue executing the code, and again you'll receive an exception. Click Break to enter Break mode, and type the following into the Command window (be sure to press Enter when done):

? txtInput.Text

The Command window prints your name.

Well, you eliminated the problem of not supplying any data to the long.Parse() method, but something else is wrong. Press F5 to continue executing the code and take a closer look at the exception text. The last statement in the text says Input string was not in a correct format. It apparently still doesn't like what's being passed to the long.Parse() method. By now, it may have occurred to you that no logical way exists to convert alphanumeric text to a number; long.Parse() needs a number to work with. You can easily test this by entering a number into the text box. Do this now by clicking Break, choosing Stop Debugging from the Debug menu, and pressing F5 to run the project. Enter a number into the text box and click the button. Code execution again stops at the break point. Press F11 to execute the statement. No errors this time! Press F5 to continue execution and C# will display the message box (finally). Click OK to dismiss the message box and then close the form to stop the project.

You can use the Command window to change the value of a variable in addition to printing the value.

Because the long.Parse() method expects a number, yet the text box contains no intrinsic way to force numeric input, you have to accommodate this situation in your code. You will learn how to deal with exceptions later on in this article using a catch statement.

Using the Output Window

The Output window is used by C# to display various status messages and build errors. The most useful feature of the Output window, for general use, is the capability to send data to it from a running application. This is especially handy when debugging applications.

You've already used the Output window if you were using C# before, but you might not have seriously considered its application as related to debugging. As you can see some data sent to the Output window by C# isn't that intuitive—in fact, you can ignore much of what is automatically sent to the Output window. What you'll want to use the Output window for is printing data for debugging. Therefore, it's no coincidence that printing to the Output window is accomplished via the Debug object.

To print data to the Output window, use the WriteLine() method of the Debug object, like this:

Debug.WriteLine("Results = " + lngResults);

The Debug object is a member of the System.Diagnostics namespace. Therefore, to define the scope for Debug.WriteLine statements, you will need to add using System.Diagnostics to the header section of your class (along with the other using statements C# creates automatically.

If you do not add using System.Diagnostics to the top of your code file, you can still access Debug. WriteLine() by writing out the full expression: System.Diagnostics.Debug.WriteLine().

Whatever you place within the parentheses of the WriteLine() method is what is printed to the Output window. Note that you can print literal text and numbers, variables, or expressions. WriteLine() is most useful in cases where you want to know the value of a variable, but you don't want to halt code execution using a break point. For instance, suppose you have a number of statements that manipulate a variable. You can sprinkle WriteLine() statements into the code to print the variable's contents at strategic points. When you do this, you'll want to print some text along with the variable's value so that the output makes sense to you. For example:

Debug.WriteLine("Results of area calculation = " + sngArea);

You can also use WriteLine() to create checkpoints in your code, like this:

Debug.WriteLine("Passed Checkpoint 1");
// Execute statement here
Debug.WriteLine("Passed Checkpoint 4");
// Execute another statement here
Debug.WriteLine("Passed Checkpoint 3");

Many creative uses exist for the Output window. Just remember that the Output window isn't available to a compiled component; calls to the Debug object are ignored by the compiler when creating distributable components.

Writing an Error Handler Using try...catch...finally

It's very useful to have C# halt execution when an exception occurs. When the code is halted while running with 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, unhandled exceptions will cause the program to terminate (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 is used to instruct C# on how to deal with an exception, rather than relying on C#'s default behavior.

C# supports structured error handling (a formal way of dealing with errors) in the form of a try block and/or catch block(s) and/or a finally block. Creating structured error-handling code can be a bit confusing at first, and like most coding principles, it is best understood by doing it.

Create a new Windows Application called Structured Error Handling. Change the name of the default form to flcsErrorHandlingExample, set its Text property to Try…Catch…Finally, and change the Main() entry point of the project to reference fclsErrorHandlingExample instead of Form1. Next, add a new button to the form and set its properties as follows:

Property

Value

Name

btnCatchException

Location

104,128

Size

96,23

Text

Catch Exception

Double-click the button and add the following code.

try
{
    Debug.WriteLine("Try");
}
catch
{
    Debug.WriteLine("Catch");
}
finally
{
    Debug.WriteLine("Finally");
}
Debug.WriteLine("Done Trying");

 

Remember to add using System.Diagnostics to the top of your class so that you can use the Debug.WriteLine() statement.

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 2 explains the sections of this structure.

Table 2. try, catch, and finally Structure

Part

Description

try

The try section is where you place code that may cause an exception. You may place all of a procedure's code within the try section, or just a few lines.

catch

Code within a general catch clause executes only when an exception occurs; it's the code you write to catch any exception. There may be multiple catch clauses to handle specific exceptions.

finally

Code within the finally section occurs when the code within the try and/or code within the catch sections completes. This section is where you place your "clean up" code—code that you want always executed regardless of whether an exception occurs.

Three possible forms of try statements are the following:

  • A try block followed by one or more catch blocks.
  • A try block followed by a finally block.
  • A try block followed by one or more catch blocks, followed by a finally block.

This example is using a try block followed by a catch block, followed by graphics/bookpencil.gifa finally block.

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:

Try
Finally

Done Trying
Here's what happened:

  • The try block begins, and code within the try section executes.
  • No exception occurs, so code within the catch section doesn't execute.
  • When all statements within the try section finish executing, the code within the finally section executes.
  • When all statements within the finally section finish executing, execution jumps to the statement immediately following the try, catch, and finally statements.

Stop the project now by choosing Stop Debugging from the Debug 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 code 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; then click the button and take a look at the Output window. This time, the text in the Output window should read

Try
Catch
Finally

Done Trying
Notice that this time the code within the catch section is executed. This is because the statement that sets lngResult causes a DivideByZero exception. Had this statement not been placed within a catch block, C# would have raised the exception and an error dialog box would have appeared. However, because the statement is placed within the try block, the exception is "caught." This means that when the exception occurred, C# directed execution to the catch section (you do not have to use a catch section, in which case caught exceptions are simply ignored). Notice also how the code within the finally section executed after the code within the catch section. Remember, code within the finally section always executes, regardless of whether an exception occurs.

Dealing with an Exception

Catching 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. Usually, you'll 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 have to 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 this 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 occurs. Run the project, click the Catch Exception button, and C# displays your custom error message

Handling an Anticipated Exception

At times, you'll anticipate a specific exception being thrown. For example, you may 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). By creating a generic Exception variable, this catch statement will catch 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 long.Parse() method? You could have used a try structure to deal with the exception, using code such as this:

long lngAnswer;
tryz
{
    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 two catch statements are 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. This second catch statement acts as a "catch all" for any exceptions that aren't Format 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.

You learned the basics for debugging applications. You learned how adding useful and plentiful comments to your procedures makes debugging easier. However, no matter how good your comments are, you'll still have bugs.

Before I conclude remember, Build errors are easier to troubleshoot because the compiler tells you exactly what line contains a build error and generally provides useful information about the error. Exceptions, on the other hand, can crash your application if not handled properly.








}