Debugging code is probably one of the most essential tasks that a developer performs. Being able to run your application and pause the execution of code midway is a lifesaver. But there is a lot more to debugging than just setting breakpoints and viewing results.
Using breakpoints, conditional breakpoints, breakpoint actions, and labels and exporting breakpoints
Using data tips
The DebuggerDisplay attribute
Diagnostic tools and Immediate Window
Attaching to a running process
Remote Debugging
Visual Studio gives developers all the required tools in order to effectively debug the code you are experiencing problems with. Without being able to debug your code, it will be virtually impossible to resolve any issues you might be experiencing.
Not being able to effectively debug your application (not knowing how to effectively use the tools you have) is just as bad as not having the tools to debug with in the first place.
Working with Breakpoints
If you are familiar with debugging in Visual Studio, this chapter might seem like old hat for you. Stick around, there might be sections discussed here that you didn’t know about.
If you are new to Visual Studio, the concept of debugging in Visual Studio is when you run your application with the debugger attached. Debugging allows you to step through the code and view the values stored in variables. More importantly, you can see how those values change.
Setting a Breakpoint
Performance Tools for Project Types
Performance Tool | Windows Desktop | UWP | ASP.NET/ASP.NET Core |
---|---|---|---|
.CPU Usage | Yes | Yes | Yes |
Memory Usage | Yes | Yes | Yes |
GPU Usage | Yes | Yes | No |
Application Timeline | Yes | Yes | No |
PerfTips | Yes | Yes for XAML | Yes |
Performance Explorer | Yes | No | Yes |
IntelliTrace | VS Enterprise only | VS Enterprise only | VS Enterprise only |
Network Usage | No | Yes | No |
HTML UI Responsiveness | No | Yes for HTML | No |
JavaScript Memory | No | Yes for HTML | No |
The Start button now changes to display Continue. Remember, at this point, your code execution is paused in Visual Studio at the breakpoint you set earlier.
Step Into (F11)
Step Over (F10)
Step Out (Shift+F11)
When you step into a method, you jump to the point in the editor where that method’s code is. If you do not want to step into the method, you can click the Step Over button or press F10 to carry on with the next line of code. If you are inside a method and want to step out and continue debugging the calling code, click the Step Out button or press Shift+F11.
Step into Specific
Imagine that we need a method that generates a waybill number based on specific business rules. Then, when the application starts, the text box field is auto-populated with the generated waybill number.
Waybill Generation Code
Form Load
If you had placed a breakpoint on the line of code that contains the GenerateWaybill() method and step into the methods by pressing F11, you would first step into method WBPartA(), then into method WBPartB() and lastly into the GenerateWaybill() method.
Simply select the method you want to step into and off you go.
Run to Click
This will advance the debugger to the line of code where you clicked, allowing you to continue stepping through the code from the new location. Quite handy if you do not want to be pressing F10 a gazillion times.
Run to Cursor
Doing this will start the debugger and set a temporary breakpoint on the line you right-clicked on. This is useful for quickly setting a breakpoint and starting the debugger at the same time. When you reach the breakpoint, you can continue debugging as normal.
Be aware though that you will be hitting any other breakpoints set before the temporary breakpoint first. So you will need to keep on pressing F5 until you reach the line of code you set the temporary breakpoint on.
Conditional Breakpoints and Actions
Sometimes you need to use a condition to catch a bug. Let’s say that you are in a for loop, and the bug seems to be data related. The erroneous data only seems to enter the loop after several hundred iterations. If you set a regular breakpoint, you will be pressing F10 until your keyboard stops working.
We will discuss Actions shortly.
You will have noticed the Actions checkbox from the Breakpoint Settings. You will also see the Actions menu on the context menu in Figure 3-8. Here you can add an expression to log to the Output Window using specific keywords that are accessed using the $ symbol.
$ADDRESS – Current Instruction
$CALLER – Previous function name
$CALLSTACK – Call stack
$FILEPOS – The current file and line position
$FUNCTION – Current function name
$PID – Process ID
$PNAME – Process name
$TICK – Milliseconds elapsed since the system was started, up to 49.7 days
$TID – Thread ID
$TNAME – Thread name
Action Expression
If you want to pause the code execution, then you need to uncheck the Continue execution checkbox.
Manage Breakpoints with Labels
This is where the Breakpoints window comes in handy. Think of it as mission control for managing complex debugging sessions. This is especially helpful in large solutions where you might have many breakpoints set at various code files throughout your solution.
The Breakpoints window allows developers to manage the breakpoints that they have set by allowing them to search, sort, filter, enable, disable, and delete breakpoints. The Breakpoints window also allows developers to specify conditional breakpoints and actions.
Compare the line numbers of the breakpoints listed in Figure 3-16 with the breakpoints displayed in Figure 3-15. You will see that this accurately reflects the breakpoints displayed in the Breakpoints window.
The only problem with this window is that it doesn’t help you much in the way of managing your breakpoints. At the moment, the only information displayed in the Breakpoints window is the class name and the line number.
You are in a better position now with the breakpoint labels set to manage your breakpoints more effectively.
Exporting Breakpoints
If you would like to save the current state and location of the breakpoints you have set, Visual Studio allows you to export and import breakpoints. This will create an XML file with the exported breakpoints that you can then share with a colleague.
I foresee the use of Visual Studio Live Share replacing the need to share breakpoints with a colleague just for the sake of aiding in debugging an application. There are, however, other situations I can see exporting breakpoints as being beneficial.
I’m not too convinced that the icons used on the import and export buttons are indicative of importing and exporting something, but that is just my personal opinion.
Using DataTips
DataTips in Visual Studio allows developers to view information about variables during a debug session. You can only view DataTips in break mode, and DataTips only work with variables that are currently in scope.
DataTips also allow you to edit the value of the variable, as long as the value isn’t a read-only value. To do this, simply select the value in the DataTip and enter a new value. Then press the Enter key to save the new value.
Visualizing Complex Data Types
The Subject Class
Convert List to DataTable
Create the List<Subject> and the DataTable
The magnifying glass icon tells us that one or more visualizers are available for the particular variable. In this example, the DataTable Visualizer.
Bonus Tip
Add a DataTip Expression
This is great if you forgot to add a variable watch or just want to see some additional info regarding the variable in the DataTip.
Using the Watch Window
The Watch window allows us to keep track of the value of one or more variables and also allows us to see how these variable values change as one steps through the code.
Here you can open the visualizer by clicking the magnifying glass icon or expand the table variable to view the other properties of the object. I use the Watch window often as it is a really convenient way to keep track of several variables at once.
The DebuggerDisplay Attribute
In the previous section, we discussed how to add a variable to the Watch window in Visual Studio. We saw that we can view the value of a variable or variables easily from this single window.
This will quickly become rather tedious, especially when you are dealing with a rather large list and you are looking for a specific value.
This is where the DebuggerDisplay attribute comes into play. We are going to modify the Subject class.
Ensure that you add the statement using System.Diagnostics to your code file.
Modified Subject Class
The use of “nq” in the DebuggerDisplay attribute will remove the quotes when the final value is displayed. The “nq” means “no quotes.”
Evaluate Functions Without Side Effects
While debugging an application, we probably do not want the state of the application to change because of an expression we are evaluating. It is, unfortunately, a fact that evaluating some expressions might cause side effects.
The Student Class
In this class, we have a HasSubjects() method that simply returns a Boolean indicating if the Student class contains a list of subjects. We also have a property called StudentSubjects that returns the list of subjects. If the list of subjects is null, it creates a new instance of List<Subject>.
It is here that the side effect is caused. If the HasSubjects() method returns false, calling the StudentSubjects property will change the value of HasSubjects().
When we call the StudentSubjects property, we see this side effect come into play in Figure 3-31. As soon as this property is called, the value of the HasSubjects() method changes.
This means that the state of our Student class has changed because of an expression that we ran in the Watch window.
This time, the value of the HasSubjects() method remains the same which means that the state of your class remains unchanged. As you have probably guessed by now, the nse added after the expression stands for “No Side Effects.”
Format Specifiers
Format specifiers allow you to control the format in which a value is displayed in the Watch window. Format specifiers can also be used in the Immediate and Command window. Using a format specifier is as easy as entering the variable expression and typing a comma followed by the format specifier you want to use. The following are the C# format specifiers for the Visual Studio debugger:
ac
Force evaluation of an expression decimal integer
d
Decimal integer
dynamic
Displays the specified object using a Dynamic View
h
Hexadecimal integer
nq
String with no quotes
nse
Evaluates expressions without side effects where “nse” means “No Side Effects”
hidden
Displays all public and nonpublic members
raw
Displays item as it appears in the raw node. Valid on proxy objects only
results
Used with a variable that implements IEnumerable or IEnumerable<T>. Displays only members that contain the query result
You will recall that we used the “nq” format specifier with the DebuggerDisplay attribute discussed in a previous section.
Diagnostic Tools
Visual Studio gives developers access to performance measurement and profiling tools. The performance of your application should, therefore, be high on your priority list. An application that suffers from significant performance issues is as good as broken (especially from an end user’s perspective).
As you debug your application, you can see the CPU usage, memory usage, and other performance-related information.
CPU Usage
In the Current Function pane, you will see the Function Body section which details the time spent in the function body. Because this excludes the calling and called functions, you get a better understanding of the function you are evaluating and can determine if it is the performance bottleneck or not.
Memory Usage
You can also compare two snapshots by clicking one of the links in the Memory Usage snapshots (Figure 3-39) and viewing the comparison in the snapshot window that opens up (Figure 3-40). By selecting a snapshot in the Compare to drop-down list, you are able to see what has changed.
The Events View
IntelliTrace events are available in this tab if you have Visual Studio Enterprise.
For a comparison of the Visual Studio 2019 Editions, head on over to https://visualstudio.microsoft.com/vs/compare/ and see what each edition has to offer.
Perftips allows developers to quickly identify potential issues in your code.
The Right Tool for the Right Project Type
The following table shows which tool Visual Studio offers and the project types that can make use of these tools.
Immediate Window
The Immediate Window in Visual Studio allows you to debug and evaluate expressions, execute statements, and print the values of variables. If you don’t see the Immediate Window, go to the Debug menu, and select Windows, and click Immediate or hold down Ctrl+D, Ctrl+I.
DisplayMessage Function
Any breakpoints contained in the function will break the execution at the breakpoint. Use the debugger to examine the program state.
Attaching to a Running Process
The available processes list allows you to select the process you want to attach to. You can quickly find the process you want by typing the name in the filter process text box.
You can, for example, attach to the w3wp.exe process to debug a web application running on IIS. To debug a C#, VB.NET, or C++ application on the local machine, you can use the Attach to Process by selecting the <appname>.exe from the available processes list (where <appname> is the name of your application).
Attach to a Remote Process
To debug a process running on a remote computer, select Debug and click Attach to Process menu, or hold down Ctrl+Alt+P to open the Attach to Process window. This time, select the remote computer name in the Connection target by selecting it from the drop-down list or typing the name in the connection target text box and pressing Enter.
If you are unable to connect to the remote computer using the computer name, use the IP and port address.
Remote Debugger Port Assignments
Visual Studio 2019: 4024
Visual Studio 2017: 4022
Visual Studio 2015: 4020
Visual Studio 2013: 4018
Visual Studio 2012: 4016
The port assigned to the Remote Debugger is incremented by 2 for each release of Visual Studio.
Reattaching to a Process
Starting with Visual Studio 2017, you can quickly reattach to a process you previously attached to. To do this, you can click the Debug menu and select Reattach to Process or hold down Shift+Alt+P. The debugger will try to attach to the last process you attached to my matching the previous process ID to the list of running processes. If that fails, it tries to attach to a process by matching the name. If neither is successful, the Attach to Process window is displayed and lets you select the correct process.
Remote Debugging
Sometimes you need to debug an application that has already been deployed to a different computer. Visual Studio allows you to do this via Remote Debugging. To start, you need to download and install remote tools for Visual Studio 2019 on the remote computer.
Remote Tools for Visual Studio 2019 enables app deployment, remote debugging, testing, profiling, and unit testing on computers that don’t have Visual Studio 2019 installed.
System Requirements
Windows 10
Windows 8 or 8.1
Windows 7 SP 1
Windows Server 2016
Windows Server 2012 or Windows Server 2012 R2
Windows Server 2008 SP 2, Windows Server 2008 R2 Service Pack 1
1.6 GHz or faster processor
1 GB of RAM (1.5 GB if running on a VM)
1 GB of available hard disk space
5400-RPM hard drive
DirectX 9-capable video card running at 1024 x 768 or higher display resolution
The remote computer and your local machine (the machine containing Visual Studio) must both be connected over a network, workgroup, or homegroup. The two machines can also be connected directly via an Ethernet cable.
Take note that trying to debug two computers connected through a proxy is not supported.
It is also not recommended to debug via a dial-up connection (do those still exist?), or over the Internet across geographical locations. The high latency or low bandwidth will make debugging unacceptably slow.
Download and Install Remote Tools
Connect to the remote machine, and download and install the correct version of the remote tools required for your version of Visual Studio. The link to download the remote tools compatible with all versions of Visual Studio 2019 is https://visualstudio.microsoft.com/downloads#remote-tools-for-visual-studio-2019.
If you are using Visual Studio 2017, for example, download the latest update of remote tools for Visual Studio 2017.
Also, be sure to download the remote tools with the same architecture as the remote computer. This means that even if your app is a 32-bit application, and your remote computer is running a 64-bit operating system, download the 64-bit version of the remote tools.
Running Remote Tools
After the installation has completed on the remote machine, run the Remote Tools application as Administrator if you can. To do this, right-click the Remote Debugger app, and click Run as Administrator.
At this point, you might be presented with a Remote Debugging Configuration dialog box. I did not encounter this window, but if you do, it possibly means that there is a configuration issue that you need to resolve. The Remote Debugging Configuration dialog box will prompt you to correct configuration errors it picks up.
You are now ready to start remote debugging your application.
Start Remote Debugging
The great thing about the Remote Debugger on the remote computer is that it tells you the server name to connect to. In Figure 3-50, you can see that the server is named DESKTOP-H1MDEFE:4024 where 4024 is the port assignment for Visual Studio 2019. Make a note of this server name and port number.
- 1.
Click the Debug tab, and check the Use remote machine checkbox, and enter the remote machine name and port noted earlier. In our example, this is DESKTOP-H1MDEFE:4024.
- 2.
Make sure that you leave the Working directory text box empty and do not check Enable native code debugging.
- 3.
When all this is done, save the properties and build your project.
- 4.
You now need to create a folder on the remote computer that is exactly the same path as the Debug folder on your local machine (the Visual Studio machine). For example, the path to the project Debug folder on my local machine is <source path> ShipmentLocatorAppVisualStudioRemoteDebuginDebug. Create this exact same path on the remote machine.
- 5.
Copy the executable that was just created by the build you performed in step 3 to the newly created Debug folder on the remote computer.
Be aware that any changes to your code or rebuilds to your project will require you to repeat step 5.
- 6.
Ensure that the Remote Debugger is running on the remote computer. The description should state that it is waiting for new connections.
- 7.On your local machine, start debugging your application and if prompted to enter the credentials for the remote machine to log on. Once logged on, you will see that the Remote Debugger on the remote computer displays that the remote debug session is now active (Figure 3-52).
- 8.After a few seconds, you will see your application’s main window displayed on the remote machine (Figure 3-53). Yep, breakfast is the most important meal of the day.
- 9.
On the remote machine, take whatever action is needed to hit the breakpoint you set earlier. I simply set a breakpoint behind the Start button click event handler. When you hit the breakpoint, it will be hit on your local machine (Visual Studio machine).
If you need any project resources to debug your application, you will have to include these in your project. The easiest way is to create a project folder in Visual Studio and then add the files to that folder. For each resource you add to the folder, ensure that you set the Copy to Output Directory property to Copy always.