The program is divided into three major parts: a class to hold customer information, a class that will return a list of customers, and the class containing the Main method that runs the
Trang 1IntelliTrace could be useful if you stepped over a statement that changed the value of
a variable and needed to go back to see what the variable value was before you stepped Figure 6-15 shows this scenario, where the highlighted event, Breakpoint hit: Main, allows you to view Locals or Call Stack The important distinction is that the values shown are for the point in time when that event occurred, not the current time, which can
be very valuable information Another important application of IntelliTrace is to inspect IntelliTrace log files that were produced by another developer or the new Microsoft Test and Lab tool that records a tester’s testing session
You can configure IntelliTrace options by selecting Tools | Options | IntelliTrace IntelliTrace will create a log file that exists as long as VS is running When VS stops, the log file is deleted, so it’s important that you copy this file before shutting down VS The location of the log file is on the Advanced branch of IntelliTrace in Tools | Options
If you receive a log file from another developer, you can load it by selecting File | Open | Open New Then you can view debugging history to view the state of the application during each event of the session
Solving Problems with VS Debugger
Previously, you’ve seen how the VS tools work and gathered a few tips on debugging This section builds upon what you’ve learned and steps you through a couple of real-world scenarios that demonstrate how to use the VS debugger to solve problems: finding and Figure 6-15 The Debug History window
Trang 2handling bad data and fixing null references The program itself is not particularly
sophisticated, but it contains just enough logic to lead you down a rat hole and show you
how to work your way out First, we’ll look at the program, and then we’ll follow up with
two bug-fixing exercises
A Program with Bugs
The code in this section contains bugs, and it’s important that you type it in as listed or
use the downloadable code for this book from the McGraw-Hill Web site I’ll describe
each piece of code and try not to give away all of the secrets of the bugs just yet Later, I’ll guide you through a process of discovery to find and fix the bugs The program is a search application that takes the first name of a person and searches for that person through a
list of customers If the program finds the customer being searched for, it will print the
customer’s first and last name Otherwise, the program will print a message stating that it
did not find the customer
The program is divided into three major parts: a class to hold customer information,
a class that will return a list of customers, and the class containing the Main method that
runs the program The following sections describe each of these classes
The Customer Class
Any time you are working with data, you’ll have a class to hold that data Since this
application works with customers, the natural approach is to have a Customer class,
as follows:
C#:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
VB:
Public Class Customer
Property FirstName As String
Property LastName As String
End Class
This is the minimal information required for this demo, and any class that you build
will have more properties Notice that both properties are type string
Trang 3The CustomerRepository Class
In this program, we create a class that is solely responsible for working with data This is a
common pattern, which is called the Repository pattern The following CustomerRepository class has a method that returns a list of Customer objects:
C#:
using System.Collections.Generic;
public class CustomerRepository
{
public List<Customer> GetCustomers()
{
var customers = new List<Customer>
{
new Customer
{
FirstName = "Franz",
LastName = "Smith"
},
new Customer
{
FirstName = "Jean "
},
new Customer
{
FirstName = "Wim",
LastName = "Meister"
}
};
return customers;
}
}
VB:
Public Class CustomerRepository
Public Function GetCustomers() As List(Of Customer)
Dim customers As New List(Of Customer) From
{
New Customer With
{
FirstName = "Franz",
LastName = "Smith"
},
New Customer With
Trang 4{
FirstName = "Jean "
},
New Customer With
{
FirstName = "Wim",
LastName = "Meister"
}
}
Return customers
End Function
End Class
The GetCustomers method returns a List<Customer> (List(Of Customer) in VB) For
the purposes of this discussion, how the GetCustomers method works won’t matter Such
a method could easily get customers from a database, Web service, or other object For
simplicity, GetCustomers initializes a List with Customer objects The part of this method
that is particularly important is the customer whose FirstName property is set to “Jean ”
Notice the blank space appended to the name, which is required to make this scenario
behave as designed (i.e., to intentionally create a bug) It’s also conspicuous that the
Customer object with a FirstName property set to “Jean ” also does not have a LastName.
The Program with Bugs
The following is a search program that uses CustomerRepository to get a list of Customer
objects The logic will iterate through the results, checking to see if the result is equal
to the search term When the result is equal, the program prints the full name of the
customer If no matching customers are found, the program indicates that the customer
wasn’t found:
C#:
using System;
class Program
{
static void Main()
{
var custRep = new CustomerRepository();
var customers = custRep.GetCustomers();
var searchName = "Jean";
bool customerFound = false;
Trang 5foreach (var cust in customers)
{
// 1 First Bug
if (searchName == cust.FirstName)
{
Console.WriteLine(
"Found: {0} {1}",
cust.FirstName,
cust.LastName);
customerFound = true;
}
}
if (!customerFound)
{
Console.WriteLine("Didn't find customer."); }
Console.ReadKey();
}
}
VB:
Module Module1
Sub Main()
Dim custRep As New CustomerRepository
Dim customers As List(Of Customer)
customers = custRep.GetCustomers()
Dim searchName As String = "Jean"
Dim customerFound As Boolean = False
For Each cust As Customer In customers
' 1 First Bug
If (searchName = cust.FirstName) Then Console.WriteLine(
"Found: {0} {1}",
cust.FirstName,
cust.LastName)
customerFound = True
End If
Next
Trang 6If (customerFound = False) Then
Console.WriteLine("Didn't find customer.")
End If
Console.ReadKey()
End Sub
End Module
Notice that the searchName variable is set to “Jean” Within the loop, the searchName
is compared with the FirstName property of each Customer instance for equality Here’s
the output from when the program runs:
Didn't find customer.
What is supposed to happen is that the program should find the matching record and
print it out, but that’s not what happens Here is the first bug, and the following discussion describes how to find the cause of the bug using the VS debugger
Finding the Bug
At this point, we know there is a bug and it’s reproducible, meaning that we can use VS
to debug and find the cause of the problem In this situation, the program is saying that it
didn’t find a Customer record or, in other words, there is no record with a FirstName of
Jean However, we know for a fact that the data does include a customer whose FirstName
is Jean We need to find out why the program cannot find it The following steps show
how the VS debugger can help isolate the problem
1. Start by setting a breakpoint on the foreach loop in the Main method This wasn’t an
arbitrary decision Instead, considering the nature of the problem, I selected a part of
the program that is likely to begin providing a cue to what the problem is Looking at
the program, one of the reasons that the program might not find the searchName is that
we aren’t getting data, causing the program to not execute the body of the foreach loop
2 Press F5 to run the program in debug mode This will execute the program and make it
stop on the foreach loop, making it possible to look at program state
3.After VS hits the breakpoint, hover over customers to see if there are any values
You’ll observe that customers does have three values The fact that there are customers
indicates that the foreach loop is executing and we’ve eliminated that as a possibility
Trang 74. Next, set a breakpoint on the if statement, right-click the breakpoint, and set the
condition as follows:
C#:
cust.FirstName == "Jean"
VB:
cust.FirstName = "Jean"
The goal here is to see what happens when the if statement finds the record matching the searchName At this point, we’re assuming that Jean does exist in the data Working
with a small program, you can use windows such as Autos, Locals, or Watch to find this record However, many real-world scenarios will give you a list with many more records Therefore, rather than waste time drilling down through dozens of records, use the VS debugger to help find the record quickly Keep in mind that all the best plans don’t always work out, as you’ll soon see, but the primary point is taking the most productive step first Setting a conditional breakpoint demonstrates how you can set conditions that can avoid eating up time caused by stepping through loops
5 Press F5 to run the program You expect to hit the breakpoint, but that won’t happen
Confusing? We know that there isn’t anything wrong with the logic, because the if
statement condition is a simple equality operator Perhaps we’ve looked in the database
or whatever source the data came from, but it’s given in this scenario that Jean is definitely in the data However, this illustrates a common problem where the quality of data you work with is less than desired
6. This time, change the breakpoint condition on the if statement as follows and re-run the
program:
C#:
cust.FirstName.Contains("Jean")
VB:
cust.FirstName.Contains("Jean")
Remember, we suspect bad data, so the call to Contains on the string assumes that there
might be some extraneous white space or other characters around the name in the data
Hover over cust.FirstName or look at cust in one of the debug windows to verify it is
the record you are looking for This breakpoint will pause on any records that contain
the sequence of characters “Jean”, such as Jean-Claude So, you might have multiple
matches that aren’t what you want The benefit is that the number of records you must
Trang 8look at is much fewer and you can save time If you have multiple records, you can
press F5 and the breakpoint will pause on each record, allowing you to inspect the
value In this case, the record set is so small that we hit the right record immediately
7 Press F10 to step over the if condition This will tell us whether the condition is being
evaluated properly In this case, VS does not step into the if statement but instead
moves to the end of the if statement, meaning that searchName and cust.FirstName are
not equal This means you need to take a closer look at cust.FirstName to see what the
problem is with the data
8.Next, we’ll use a couple of the VS debugger tools to inspect cust.FirstName and find
out why the equality check is not working Open the Immediate window (CTRL-D, I) and execute the following expression:
cust.FirstName
which will return this:
"Jean "
Here, you can see that the result has a trailing space—dirty data Clearly, “Jean” does
not equal “Jean ” because of the extra character in the data There are various
non-printable characters that could show up, and VS can help here too
9.Open a Memory window (CTRL-D, Y), type cust.FirstName into the Address box, and
press ENTER This will show the hexadecimal representation of the data at the memory
location of the variable, shown in Figure 6-16
The layout of the Memory window starts with an address on the left, which is
scrolled down to the line where the data in cust.FirstName variable first appears
In the middle is the hex representation of the data The final column has a readable
Figure 6-16 The Memory window
Trang 9representation of the data where any characters that don’t have a readable representation
appear as dots You can see “.J.e.a.n.” on the first line of the third column .NET
characters are 16-bit Unicode, and the data for the character only fills the first byte, resulting in the second byte being set to 00, causing the dots between characters you see in the first column If the data used another character set, such as Japanese Kanji, you would see data in both bytes of the character The hex representation of this data
in the second column is “00 4a 00 65 00 61 00 6e 00 20” Looking at the Unicode
representation, which you can find at http://unicode.org/, you’ll see that the hex and visual representation of the characters match
You can see that I’ve highlighted the 00 20 at the end of the first line of the second column in Figure 6-16, which proves that Jean is followed by a Unicode space character
Knowing this information might help you share information with someone who is responsible for the data, letting them know that there are extraneous spaces in the data Some computer or software systems might even use other types of characters, perhaps
a proprietary delimiter for separating data, and accidentally save the data with the delimiter
Fixing the First Bug
While you might have bad data and it might not be your fault, the prospect of fixing the problem by fixing the data source is often illusive, meaning that you need to apply a fix
in your code In this section, we’ll apply a fix However, we’ll put a convoluted twist in the solution where we discover a new bug when fixing the first The purpose is twofold:
to illustrate the real-world fact that there are often multiple problems with a given piece of code and to show a completely different type of bug that you will encounter when writing your own code The following steps lead you through the fix and subsequent discovery of the new bug:
1 Press SHIFT-F5 to stop the previous debugging session
2. Implement a fix by commenting out the contents of the foreach loop and replacing with
code that protects against extraneous spaces in the data, as follows:
C#:
var firstName = cust.FirstName.Trim();
var lastName = cust.LastName.Trim();
if (searchName == cust.FirstName)
Trang 10{
Console.WriteLine(
"Found: {0} {1}",
firstName,
lastName);
customerFound = true;
}
VB:
Dim firstName As String = cust.FirstName.Trim()
Dim lastName As String = cust.LastName.Trim()
If (searchName = cust.FirstName) Then
Console.WriteLine(
"Found: {0} {1}",
cust.FirstName,
cust.LastName)
customerFound = True
End If
Next
Notice that the fix was to use the string.Trim method to remove the extraneous space
from the data, assigning the clean results to local variables Trim defaults to using the
space character but has overloads that allow you to specify a different character, just in
case the actual character you saw in Figure 6-16 was something other than a space The rest of the logic uses variables with the clean data
3.Press F5 to run the program and see if the fix works Unfortunately, you’re stopped in
your tracks by the fact that a new error occurs: a NullReferenceException Unlike runtime
errors that give you wrong data, VS helps greatly by breaking on exceptions when they
occur in the code The next section describes this error, the NullReferenceException, in
greater detail and provides information to help you deal with the problem when it occurs
in your programs
Debugging and Resolving
NullReferenceException Problems
Encountering a NullReferenceException in your code is a common occurrence, deserving
some discussion to help you deal with these problems effectively As described in Step 3
in the preceding section, VS will pause on a NullReferenceException when running