The Run execution through here green glyph can be transformed into set next statement to here by holding the key Ctrl. It is different than Run execution through here because the statement in between is not executed. Hence in the small animation below, we can see in the watch window that the reference obj remains null: MyClass constructor in between hasn't been executed.
Data breakpoint hit: when value changes
If you set a breakpoint to a non-static property setter it will be hit when changing the property value for all objects. The same behavior can be obtained for a single object thanks to the local (or watch) window right-click: Break when the value changes the menu.
This facility is illustrated with the animation above. The hit occurs only when obj2.Prop is changed, not when obj.Prop is changed.
Note that a data breakpoint is bound to a live object during a debugging session. Hence it gets lost once the debugged process stops, it cannot be reused during the future debugging sessions.
Note that the menu breaks when value changes are also available when right-clicking a field in the local's window but unfortunately the debugger doesn't break on-field change, I am not sure if it's a bug or a feature not yet implemented?
Conditional Breakpoint
A condition can be attached to a breakpoint to break only in a certain scenario. In the animation below we define the breakpoint with the condition I > 6 within the loop. Then we click Continue and can see that once the breakpoint is stopped, the Ivalue is actually 7.
Trace Breakpoint
Halting the program execution is the most common action upon a breakpoint hit. However, you can choose instead to print some traces in the Output window without (or with) halting. This possibility is illustrated by the animation below where we trace the value of I from 0 to 9 in the Output window. Notice that a trace breakpoint has the diamond shape in the code editor gutter.
Note that both a condition and a trace action can be specified on a breakpoint.
Tracking an object whose reference goes Out-Of-Scope
In the Watch, window objects are tracked by the name of their references in the currently executed scope. However when such tracked reference goes out-of-scope, it becomes meaningless in the context of the Watch window and it gets disabled, even though the referenced object is still alive.
There are many situations where we’d like to continue tracking the state of an out-of-scope object. To do so, right-click such reference in the Watch window, click the menu Make Object ID and add $1 in the items to watch (or $2 or $3… depending on how many object IDs you’ve already created).
The animation below shows how to track the state of an out-of-scope object’s property getter that returns the actual date-time as a string. It shows well that when the reference obj goes out-of-scope in the context of Fct(), obj item to watch gets disabled and $1.Prop still gets updated.
View the value returned by a function
The value returned by a function is sometimes ignored by the source code. Or sometime this value is just not obviously accessible at debug-time.
Such returned value can be shown in the Debug > Windows > Autos windows. The
pseudovariables$ReturnValue can also be used in the
Immediate and
Watch Windows view the last function call returned value.
Note that the menu Debug > Windows > Autos is available only when the Visual Studio debugger is attached to a process and the program is halted by the debugger.
Reattach to Process
Since Visual Studio 2017 the Reattach to processShift+Alt+P facility is proposed and it’s very handy. Once you’ve been attaching the debugger to a process Visual Studio remembers it and proposes to re-attach the debugger to the same process. Same is in italic because there is a heuristic here about the process identity:
- If the process you’ve been attached to is still alive Reattach to process re-attach to it.
- Else Visual Studio attempts to find a single process with the same previous process name and re-attach the debugger to it.
- If several processes are found with this name, the Attach to Process dialog is opened with only those processes with the same name shown
- If no process with this name can be found the Attach to Process dialog is shown
Reattach to Process also works with debug sessions involving multiple processes. In this situation, Visual Studio attempts to find all processes it has been attached to with the same heuristics explained above.
Process No-Side-Effect expression evaluation
Sometime when evaluating an expression in the Immediate or in the Watch window some state gets changed. This behavior is often undesirable, you don’t want to corrupt the state of your debugged program just because you needed to evaluate the value of an expression.
To avoid changing any state you can suffix your expression with, nse (No-Side-Effect). This possibility is illustrated by the animation below (watch the _State value changing or not in the Watch window),
Here nse is used in the Watch window. This sample is less trivial than the previous one because of the Refresh evaluation button in the SideEffectFct() watched item.
No Side Effect expression evaluation in the Watch Window
Show Threads in Source
Debugging a multithreaded application is notoriously complex. Hopefully, the Show Threads in Source button can help a lot. It introduces marker icons in the editor gutter to keep track of the locations on which other threads are halted. This marker can be used to show the thread ids and eventually switch to another thread. Notice that a different marker glyph is shown if at least two threads are halted in the same location.
Here is the source code of this small demo if you’d like to play with it,
- using System;
- using System.Threading;
- class Program {
- static void Main() {
- for (int i = 0; i < 5; i++) {
-
- int j = i;
-
- if (j == 1) {
- j = 0;
- }
- ThreadPool.QueueUserWorkItem(delegate {
- Method(j);
- });
- }
- Thread.Sleep(60000);
- }
- static void Method(int id) {
- switch (id) {
- case 0:
- Thread.Sleep(60000);
- break;
- case 1:
- Thread.Sleep(60000);
- break;
- case 2:
- Thread.Sleep(60000);
- break;
- case 3:
- Thread.Sleep(60000);
- break;
- case 4:
- Thread.Sleep(60000);
- break;
- }
- }
- }
Decompile IL Code to Source Code that can be debugged
Often we depend on some black-box components: assemblies for which we don’t have the source code.
However, when debugging a complex behavior it is convenient to observe and even debug the logic nested in black-boxes referenced. This is why since version 16.5 Visual Studio 2019 can generate some source code from compiled assemblies. Such source code is then debuggable. This feature is based on the OSS project
ILSpy.
The decompilation menu can be proposed in the assembly right-click menu in the Modules window (as shown in the animation below) and in the Source Not Found or No Symbols Loaded dialogs.
Conclusion
Visual Studio shines but it especially shines when it comes to debugging. Here I tried to select some tips that are both quite hidden but often useful, I hope they will help improve your productivity.
..... Keep Learning !!!!