Debugging your .NET application more easily
As a programmer, you often have to debug your code, or the code written by a coworker. I don't like debugging, but as you know:
If debugging is the process of removing bugs, then programming must be the process of putting them in.
Unless you are an extraordinary developer, you'll need to debug your code. Visual Studio provides great tools to help you with this task. The main features are breakpoints, step by step execution, and viewing the value of the variables:
By default, the debugger doesn't always show useful values. In the previous screenshot, you cannot quickly distinguish the 2 customer instances. Let's see how to improve that and thus, reduce the time needed to debug your application!
#Override the ToString method
By default, the value of the ToString
method is shown. So, the first way to change the display value is to override the ToString
method and return something more useful:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Order> Orders { get; } = new List<Order>();
public override string ToString()
{
return $"{FirstName} {LastName}";
}
}
#Use the DebuggerDisplay attribute
When you don't want to override the ToString
method just for debugging purposes, you can use the DebuggerDisplay
attribute. The value of the attribute uses a format similar to the syntax introduced in C# 6 with string interpolation. However, the text between brackets are expressions to be evaluated by the debugger engine. So, an expression can be a property name or a more complex expression such as {Count > 0 ? Count : 0}
.
[DebuggerDisplay("{Id} - {FullName} ({Orders.Count} orders)")]
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Order> Orders { get; } = new List<Order>();
public string FullName => $"{FirstName} {LastName}";
}
Evaluating an expression by the debugger is expensive, so you should not have too many expressions in the display value. Instead, you can create a private property in your class, and use only this property in the DebuggerDisplay
attribute.
[DebuggerDisplay("{DebuggerDisplay,nq}")] // nq means no quote
public class Customer
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"{Id} - {FullName} ({Orders.Count} orders)";
}
I use the nq
format to prevent Visual Studio from adding quotes around the value. You'll find valid formats in the documentation.
Note that you can also add the attribute to a type you don't own by adding the attribute at the assembly level and by specifying the Target
property:
[assembly: DebuggerDisplay("{Host}", Target = typeof(Uri))]
#Hide code to the debugger
You can annotate your code with some attributes to change the behavior of the debugger when you run step by step. For instance, you don't want the debugger to step into generated code. In this case, you can add one of the following attributes:
DebuggerNonUserCodeAttribute
tells the debugger that the code it is applied to is not My Code.DebuggerHiddenAttribute
hides the code from the debugger, even if Just My Code is turned off. The method is not visible in the Call Stack window.DebuggerStepThroughAttribute
tells the debugger to step through the code it is applied to, rather than step into the code.
static void Main()
{
GeneratedMethod(); // Step into...
}
[DebuggerNonUserCode]
private static void GeneratedMethod() // show as External Code in the call stack
{ // do not step here
MyCode();
}
private static void MyCode()
{ // ... step here
}
The GeneratedMethod
method is not visible in the call stack:
Call Stack with DebuggerNonUserCodeAttribute
#Use the DebuggerTypeProxy attribute
The DebuggerTypeProxyAttribute
attribute is used to specify a display proxy for a type, allowing a developer to tailor the view for the type. If the attribute is found, the expression evaluator substitutes the display proxy type for the type the attribute is applied to. This is useful when you want to expose properties that are not in the original type.
[DebuggerTypeProxy(typeof(SampleDebugView))]
public class Sample
{
public string Name { get; set; }
private class SampleDebugView
{
private readonly Sample _sample;
public SampleDebugView(Sample sample)
{
_sample = sample;
}
public string Name => _sample.Name;
public int NameLength => _sample.Name.Length;
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public char[] NameCharacters => _sample.Name.ToCharArray();
}
}
You can check real examples in the .NET framework: https://github.com/dotnet/runtime/search?q=DebuggerTypeProxy&type=Code
#Conclusion
The Visual Studio debugger is very great. It allows you to quickly debug your code by using breakpoints, viewing the value of the variables, and more. You can customize it by adding some attributes to your code. These attributes should help you in reducing the time needed to debug your application.
Do you have a question or a suggestion about this post? Contact me!