asp:Feature
LANGUAGES: C#
ASP.NET
VERSIONS: 2.0
Search & Destroy 101
Debugging Windows Forms Run-time Errors
By Asif Sayed
How often do you hear from one of your peers, I m busy
debugging my code ? As I see it, debugging and code development go hand-in-hand
for every developer. Although debugging is becoming easier and more innovative
with every release of Visual Studio, beginning developers can find it very
challenging. In this article, I ll show you a simple step-by-step tutorial for
finding bugs that will help you in becoming a code warrior (see Figure 1). I
assume the reader has a basic understanding of the Visual Studio 2005 IDE. You
should also be comfortable writing code with C#.
Figure 1: Example in run-time mode.
What Is Debugging?
To understand what debugging is, you must know what a bug
is! Put simply, a bug is any unwanted result of code. So, what does a bug do? Simple;
it produces an error and you know what happens when an error is produced. Debugging
is the process by which you remove such bugs and prevent errors.
The quality of an application is partly determined by the
amount of debugging effort spent on it. No wonder we usually build a project in
debug mode. However, no matter how much care is taken, it s nearly impossible
to make truly bug-free code. Even the most experienced developer can introduce
a bug by making a silly mistake. However, with the strong debugging support
built in to Visual Studio, we can easily tackle even the most notorious bug.
Different Types of Errors
What are the different types of errors that bugs can
produce? Let s divide them into the following three categories:
- Design-/compile-time error (mostly syntax
errors)
- Run-time error
- Logical error
Design-time errors are the most friendly of errors,
because most of them can be identified during compilation and fixed without
much hassle. The Visual Studio IDE features IntelliSense, which helps highlight
potential errors before you even compile. Run-time errors which often cause a
panic are bad, especially if they crash your application during a demo
session. Last on the list is the logical error. Here you graduate from
panicking to hair pulling.
Bugs that produce logical errors are nasty because the compiler
doesn t complain about them, so you won t get a run-time error. However, you also
won t get the result you expect. In such scenarios, the debugger is most handy.
Imagine you are calculating average sales for each month. The formula is
simple, average sales per month = total sales / 12 . But maybe at some point
in your code you are dividing by 13 instead of 12. The result won t be correct.
Debugging will help find and replace the error.
To demonstrate the technique of debugging, we must create
a bug (a rare case where you are deliberately trying to create a bug). Creating
a run-time bug is not very difficult, and you can probably see many situations
in the preceding average sales example that will result in a run-time error.
For example, how about Divided by Zero ? Or Array index out of the range
error?
Let s go ahead and create a simple application that will
produce a table of two and store the result in an array of 10 subscripts. We ll
introduce the error in a loop, in which we read from the array and display the
table using a multiline TextBox control (see Figure 2); one button will produce
the table and another one will result in a run-time error.
Let s create a Windows Application project following these
steps:
- Click on menu File | New | Project, or you can
press the hot key, Ctrl + Shift + N (again, see Figure 2).
- From the New Project dialog box select Visual C#
| Windows.
- From Templates select Windows Application.
- Name the application; I ve called the project WinDebug101 .
You may choose a different location for storing the application files according
to your preference.
- Click the OK button to finish the process. VS
2005 will create a new project; your project should resemble Figure 3.
Figure 2: Create a new Windows
Application project.
Figure 3: The newly created project.
Most of you will see something similar to the project
shown in Figure 3. You should see a blank Form1 created for you because of a new
project. First, let s set a couple of properties of Form1 according to the
properties listed in Figure 4. If the property toolbox is not visible in the
IDE, hit the F4 key to make it visible. Pease make sure to select Form1 before
applying changes to properties using the property toolbox.
|
Property
|
Value
|
|
Text
|
Windows Forms debugging 101
|
|
Size
|
300, 300
|
Figure 4: The properties
of Form1.
Now let s add controls to the newly created Form1. As you
can see in Figure 1, this example is a simple test harness for demonstrating
the creation of a bug. We ll have one TextBox control to enter and format text,
and two Button controls to interact with the database. Let s start by adding
these controls on Form1. Make sure your Form1 looks similar to that shown in Figure
5.
Figure 5: Form1 designer surface
after adding controls.
Please change the properties of the controls on the form
according to Figure 6. Properties can be changed by selecting the control and
using the properties toolbox. Figure 7 illustrates a graphical illustration of
the process.
|
Control
|
Property
|
Value
|
|
textBox1
|
Multiline
|
True
|
|
Button1
|
Text
|
Table with no Bug
|
|
Button2
|
Text
|
Table with Bug
|
Figure 6: Properties
of the controls on Form1.
Figure 7: Changing the properties of
controls on Form1.
Now let s write some C# code to bring life to Form1. Make
sure the code-behind of Form1 looks like that shown in Figure 8.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WinDebug101
{
public partial class
Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void
button1_Click(object sender, EventArgs e)
{
string[] TableOfTwo =
new string[10];
int i;
// loop to calculate
the table
for (i = 0; i <
10; i++)
{
// 2 x 1 = 2...
TableOfTwo[i] =
"2 x " + Convert.ToString(i+1) +
" = " + Convert.ToString(2 * (i +
1));
}
// loop to display
the table
for (i = 0; i <
10; i++)
{
// 2 x 1 = 2...
textBox1.Text =
@textBox1.Text +
TableOfTwo[i] +
"\r\n";
}
}
private void
button2_Click(object sender, EventArgs e)
{
string[] TableOfTwo =
new string[10];
int i;
// loop to calculate
the table
for (i = 0; i <
10; i++)
{
// 2 x 1 = 2...
TableOfTwo[i] =
"2 x " + Convert.ToString(i + 1) +
" = "
+ Convert.ToString(2 * (i + 1));
}
// loop to display
the table
for (i = 0; i <
11; i++)
{
// 2 x 1 = 2...
textBox1.Text =
@textBox1.Text +
TableOfTwo[i] +
"\r\n";
}
}
}
}
Figure 8: Bring
life to Form1.
Code Analysis
As you can see, the code is a simple loop to produce an
array containing two items. The first loop does the calculation and stores the
result in an array. The second loop reads through the array and displays the
result in a multi-line textbox. The code inside the button1_click event is the good
code that will calculate and display the table output without error.
The code inside the button2_click event is the bad code,
which will produce a run-time error because of the bug we introduced. Can you
spot the bug? If you see the type of error, it s easy to figure out where the
bug is. If you look back at Figure 1, it says Index was outside the bounds of
the array. This means the loop that displays the result is trying to access an
array item that is outside its limits. So, the only difference between the code
in both button event handlers is:
// loop to display the table
for (i = 0; i < 11; i++)
{
// 2 x 1 = 2...
textBox1.Text =
@textBox1.Text + TableOfTwo[i] + "\r\n";
}
As soon we try to access the eleventh physical array
element, this bug causes the program to come to an immediate halt, resulting in
the run-time error. You may also notice I m purposely not using try...catch
here so that I can display the error within the IDE.
Now let s build and run the example. We can build the
project several ways. If you have one project in the solution, then building
the solution and the project are essentially the same. You can click the small
green Play button on the main toolbar, or hit F5 on the keyboard to start the
application. Also, make sure to build using debug mode (see Figure 9).
Figure 9: Project build.
If all goes well and the program compiles, you should be
able to run the application. Click the Table with no Bug button; this will
cause the textbox to be populated with 10 values. If you click the Table with
Bug button instead, the IDE will complain about the run-time error (which
should look similar to what is shown in Figure 1).
Now let s debug our run-time error. As you can see in
Figure 1, when a run-time error occurs, the execution of the program halts
where it encounters the error. In our example, the execution stops while
running the loop, which displays the table. Here s the code snippet:
// loop to display the table
for (i = 0; i < 11;
i++)
{
// 2 x
1 = 2...
textBox1.Text
= @textBox1.Text + TableOfTwo[i] + \r\n ;
}
When the program stops at the line inside the loop, what
does it mean to you as a developer? Obviously this line has some functionality that
is violating something! What other information is given to you by the IDE to
help you get rid of this bug?
When an error happens, the code throws an exception. This
is your primary channel of information as to what has happened and why. In our
example, the type of the exception is IndexOutOfRangeException. What does it
mean? As you can see, we re using an array, and the type of exception is
telling you something is wrong with the index range. The more bugs like this
you encounter, the better your debugging skills will get and the faster you
will be able to detect and correct these types of errors.
In addition to the exception details, Visual Studio also
provides some different feedback windows that can help you quickly diagnose and
resolve the error. One such feedback window is the Locals window, which
displays all the locally scoped variables and their values. As you can see in
Figure 10, we can see the current contents of the TableOfTwo[]array here.
Usually by analyzing the value of a variable, you can easily spot the bug.
Figure 10: Feedback windows in debug
mode.
The Watch window is another helpful debugging feature you
can use to monitor the value of any given object. Let s say you d like to track
the value of the fourth physical element of the array. You can do so by
switching to the Watch window (in this case Watch1) and type TableOfTwo[3] , then
press Enter to see that the value column will display 2 x 4 = 8 (see Figure 11
for a graphical illustration).
Figure 11: The Watch window in
action.
In many cases, you ll need to step through the code line
by line until you encounter the error. This debugging method will usually give
you a better idea of what happened to cause the error. This process can be done
easily while in debug mode. The first step is to decide which line of code on
which to pause the execution of the running application. This is commonly
referred to as setting a break point, and can be accomplished by either
clicking in the gray area just to the left of the line of code or by pressing
F9. A red bullet mark will appear in the gray area if the break point was
successfully set.
Once the application is paused, you can take control of
the code execution and move step-by-step until you hit the error. In this case,
it makes sense to set the break point inside the loop on the for statement so
you can query the values of the current array item for each execution of the
loop (see Figure 12 for the breakpoint setup).
Figure 12: Setting a break point.
As you can see in Figure 13, once the breakpoint is set up
properly, you can step through the code in the following ways:
- Step Into Debug | Step Into (F11 key)
- Step Over Debug | Step Over (F10 key)
- Step Out Debug | Step Out (Shift + F11 key)
Figure 13: Stepping through in debug
mode.
Step Into means execute the current line of code and if
the next line is a call to another method, the yellow code pointer will move to
that code segment. Step Over means the yellow pointer will move to the next
line in the current code segment without following in to any local methods that
might be called by the current line. Step Out means completely execute the code
in the current method and move the yellow pointer to the next line in the
calling method.
If you d like the program execution to continue as normal,
select Debug | Step Over or hit F5. You also can start and stop debug by
selecting the appropriate command from the Debug menu. You also can access all
these features with the help of the Debug toolbar (see Figure 14).
Figure 14: The Debug toolbar.
Conclusion
As you can see, it is easy to use the powerful debugging
features of the Visual Studio IDE. With this article I introduced the debugging
process, which I hope the beginner-level audience will find especially useful
in finding and fixing errors in their code. Thank you for reading; as always, I
look forward to your comments and suggestions feel free to drop me a line at mailto:asif.blog@gmail.com.
Files accompanying
this article are available for download.
Asif Sayed has more
than 15 years of experience in software development and business process
architecture. A senior systems analyst with Direct Energy in Toronto,
Canada, he also teaches
.NET technologies at Centennial College
in Scarborough, Ontario.
He is the author of Client-Side Reporting with
Visual Studio in C# (Apress, 2007). Contact him at mailto:asif.blog@gmail.com.