Boost your productivity with Visual Studio’s DebuggerAttributes

This post originally appeared on Moaid Codes

 

Visual Studio is a great IDE with a great Debugger. It currently provides one of the best debugging experiences out there. The extensibility and the customizability of Visual Studio makes it even better. For example, it provides Attributes for customizing the display of objects during debugging sessions. By utilizing these attributes we can decrease debugging time.

Assuming we need to examine a collection of objects during a debugging session when the objects are of type Scientist. By hovering over the collection, Visual Studio will list the objects using DataTips in the following manner:
List of objects

These DataTips aren’t much helpful since we can’t see any information about the objects without expanding each one of them. This could increase debugging time if the collection is long.

One way to improve these DataTips is by overriding the ToString method of the Scientist class. For the following implementation of ToString:

public override string ToString() 
    => $"Name: {Name}, Birthday: {Birthday}";

This is how the collection will be displayed:

List of objects with ToString implementation

This is much better, but this isn’t always a viable option. For example, when we don’t want to override the ToString method only for debug purposes or when we completely can’t, for instance, when the class is defined in a third-party assembly.

Fortunately, Visual Studio provides ways to customize how objects are displayed during debug sessions by using Debug Attributes.

With DebuggerDisplayAttribute we can define how objects are displayed when hovering over a variable. The Attribute can be set either on the class level (Above a class) or on an assembly level for when we can’t modify the class.
The attributes expects a string similar to C#’s 6 String interpolation format and an optional TargetType that is only necessary when we define the attribute on an assembly level.

For our example, we can define the attribute directly over the class:
DebuggerDisplayAttribute on a class level

Or on an assembly level:
DebuggerDisplayAttribute on an assembly level

Note: Instead of using the Target parameter which accepts a Type, we could use TargetName which accepts a fully qualified name of a class. This is handy when the attribute is applied on an assembly level that don't reference the assembly defining the Type.

In both cases, the result will be the same:
List of objects with a DebuggerDisplay attribute

Defining attributes for each type we might encounter while debugging isn’t too hard, but sometimes it might harm productivity. You’ll have to stop a debug session just for placing a new attribute or for changing how DebuggerDisplayAttribute displays a type. In addition, this require a code modification that you’ll have to decide whether to commit to your source control or not.

Fortunately, OzCode has a neat feature called Reveal that solves this problem. It can change how objects are displayed at runtime without interrupting the debug session and without modifying your code. Just press on the ‘Star’ icon of each property you want to display.
List of objects with OzCode's Reveal feature

Now, what about how each object is displayed when expanding it? By default Visual Studio will display all of its properties and fields:
How objects are displayed by default when expanded

This might be ok in most of the times, but sometimes either the object has too many fields or it might be missing fields or properties that might be helpful for debugging.

Luckily, Visual Studio provides a way to change completely how each object is displayed when it is expanded. This is done by using the DebuggerTypeProxyAttribute with a class that we will write to expose only the necessary properties and fields.

Assuming the Awards of each scientist aren’t necessary for our current debugging session and assuming we usually need to calculate how many years has passed since the current scientist was born, we implement the following class:

class ScientistTypeProxy
{
    private readonly Scientist _scientist;

    public ScientistTypeProxy(Scientist scientist)
    {
        _scientist = scientist;
    }

    public string Name => _scientist.Name;
    public string[] Fields => _scientist.Fields;
    public int YearsPassed => _scientist.Birthday.YearsPassedSince();
}

We can apply the DebuggerTypeProxyAttribute directly over the Scientist class or on the assembly level, similar to the DebuggerDisplayAttribute.
DebuggerTypeProxyAttribute Class Level

Or by the class Level:
DebuggerTypeProxy on the Assembly Level

This also might interrupt the debugging session and require code modifications. Fortunately, OzCode’s Custom Expression to the rescue! It provides the ability to add custom properties to be displayed when expanding an object during debugging. These Custom Expression’s won’t modify the actual classes, so no code modifications are necessary.
OzCode's Custom Expression Feature

This is already better, but we still need to expand the array in order to see the values, or at least add a property to the DebuggerTypeProxy’s class or a Custom Expression using Ozcode to aggregate the array to a string. This is acceptable, but Visual Studio provides the DebuggerBrowsableAttribute specifically for that. Using this attribute, we can hide members of a class or even expand arrays by default using the RootHidden argument.

By applying the following attribute with RootHidden over the Fields member in ScientistTypeProxy
DebuggerBrowsable over Fields with RootHidden

We receive the following result:
Result of DebuggerBrowsableAttribute with RootHidden over Fields

This is even better but we can go even further. For complex data, like collections, values for plotting graphs or for visualizing any complex model; properties and fields might not be the best way to debug the values of an object.
With the DebuggerVisualizerAttribute we can get total control on how objects are displayed. Actually, using a WinForms/WPF window, we can even plot graphs, display images and do whatever is needed to better debug such objects.

For example, the following class is a custom visualizer that can show the scientist’s picture if it is available

public class ScientistVisualizer : DialogDebuggerVisualizer
{
    public ScientistVisualizer()
    {
    }

    protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
    {
        var scientist = (Scientist)objectProvider.GetObject();

        Window window = new Window()
        {
            Title = scientist.Name,
            Width = 400,
            Height = 300
        };

        var images = new Dictionary<string, string>;
        {
            ["Marie Curie"] = "Marie_Curie.jpg",
            ["Maria Goeppert-Mayer"] = "Maria_Goeppert-Mayer.jpg",
            ["Rosalind Franklin"] = "Rosalind_Franklin.jpg",
            ["Barbara McClintock"] = "Barbara_McClintock.jpg",
        };

        if (images.ContainsKey(scientist.Name))
        {
            string imageName = images[scientist.Name];

            window.Background = new ImageBrush(new BitmapImage(new Uri(string.Format("pack://application:,,,/{0};component/Images/{1}", typeof(ScientistVisualizer).Assembly.GetName().Name, imageName))));

            window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            window.ShowDialog();
        }
    }
}

We have to add the DebuggerVisualizerAttribute for the class we want to visualize. Similarly to the rest of the attributes, we can add it over the class or on the assembly level.

In addition, we have to add the SerializableAttribute over the class being visualized.
DebuggerVisualizerAttribute over class

Or by the assembly level
DebuggerVisualizerAttribute on the assembly level

As a result, by pressing on the “Magnify” icon next to each item, an image of the scientist appears:
DebuggerVisualizer Magnify icon

Examples:
Custom Visualizer visualizing Marie Curie

Custom Visualizer visualizing Barbara Mclintock

Because the custom visualizer has to extend the type DialogDebuggerVisualizer and because it opens a WPF window, we had to add references for:

  • Microsoft.VisualStudio.DebuggerVisualizers.dll
  • PresentationCore.dll
  • PresentationFramework.dll
  • System.Xaml.dll
  • and WindowsBase.dll

Because of this, it is usually better to define the Visualizer in a separate assembly.

This is great, but in case of a long collection, it might still be hard to find the items we are interested in. Luckily, OzCode has a collection filtering feature and a Search feature that are really handy in this situations.

You can find all of the classes above in this GitHub Gist or in this GitHub Repository.

Moaid Hathot

I am a C#/.Net Developer, Consultant and OzCode Evangelist at CodeValue. I am an enthusiastic developer that strives to learn something new every day. One of my greatest passions is programming and I aspire to master it.

  • Oswald Umeh

    This article just unearthed gems that were up till now hidden from the likes of me!
    Thanks for sharing.

  • Boopathy S

    Thank you to your information… This is details in the http://edu.microdots.in/ site is awesome in correct way of modify ..

  • Carsten Schütte

    It would be great of OzCode would handle [DebuggerNonUserCode] correctly and not break on exceptions that occur in methods with this attribute – unless activated in settings.